SaltyCrane: djangohttps://www.saltycrane.com/blog/2019-02-20T21:44:39-08:00Creating a GraphQL API with Python, Graphene, and Postgres
2019-02-20T21:44:39-08:00https://www.saltycrane.com/blog/2019/02/creating-graphql-api-python-graphene-and-postgres/<p>
Here are my notes on creating a <a href="https://graphql.org/">GraphQL</a> API
with Python, Django, <a href="https://graphene-python.org/">Graphene</a>, and
Postgres. I learned almost everything from the
<a href="https://www.howtographql.com/graphql-python/0-introduction/">
excellent GraphQL Python tutorial at howtographql.com</a
>. Optimistically, I'll write a series of posts about how to make a React
Native app that no one will use. Realistically, I won't.
</p>
<h4 id="contents">Contents</h4>
<ul>
<li><a href="#install-python">Install Python</a></li>
<li>
<a href="#create-directory">Create project directory and virtualenv</a>
</li>
<li><a href="#django">Install Django and create a Django project</a></li>
<li><a href="#postgres-docker">Run Postgres in Docker</a></li>
<li><a href="#create-database">Create a database</a></li>
<li><a href="#django-postgres">Configure Django to use Postgres</a></li>
<li><a href="#graphene">Install and configure Graphene</a></li>
<li><a href="#django-app">Create a new Django app and add a model</a></li>
<li><a href="#graphql">GraphQL all the things</a></li>
<li><a href="#try">Try it using the GraphiQL explorer</a></li>
<li><a href="#references">References / See also</a></li>
</ul>
<h4 id="install-python">Install Python 3.7.2</h4>
<pre class="console">
$ brew install python
</pre>
<h4 id="create-directory">Create project directory and virtualenv</h4>
<pre class="console">
$ # make project directory
$ mkdir travelog-api
$ cd travelog-api
$ # make virtualenv
$ python3 -m venv venv
$ # activate virtualenv
$ source venv/bin/activate
$ # upgrade pip
$ pip install --upgrade pip
</pre>
<h4 id="django">Install Django and create a Django project</h4>
<p>
(Still in the <code>travelog-api</code> directory with virtualenv activated)
</p>
<ul>
<li>
Install Django 2.1.7:
<pre class="console">$ pip install Django </pre>
</li>
<li>
Create Django project:
<pre class="console">$ django-admin startproject travelog_api ./</pre>
</li>
<li>
Run migrations and run the server:
<pre class="console">
$ ./manage.py migrate
$ ./manage.py runserver
$ # go to http://localhost:8000 in the browser</pre
>
</li>
</ul>
<h4 id="postgres-docker">Run Postgres in Docker</h4>
<p>
(Still in the <code>travelog-api</code> directory with virtualenv activated)
</p>
<ul>
<li>
<a href="https://docs.docker.com/docker-for-mac/install/"
>Install Docker for Mac</a
>
</li>
<li>
Create a new file, <code>travelog-api/docker-compose.yml</code>:
<pre>
version: "3.7"
services:
db:
image: "postgres:11.2"
container_name: "travelog_postgres1"
ports:
- "54321:5432"
volumes:
- postgres_data1:/var/lib/postgresql/data
volumes:
postgres_data1:
name: travelog_postgres_data1</pre
>
</li>
<li>
Start Postgres
<pre class="console">
$ docker-compose up -d
$ docker-compose logs</pre
>
</li>
</ul>
<h4 id="create-database">Create a database</h4>
<ul>
<li>
Start psql:
<pre class="console">
$ docker exec -it travelog_postgres1 psql -U postgres</pre
>
</li>
<li>
Create a database (be sure to include the semicolon):
<pre>postgres=# create database travelog;</pre>
</li>
<li>
Create user:
<pre>
postgres=# create user traveloguser with password 'mypassword';
postgres=# grant all privileges on database travelog to traveloguser;</pre
>
</li>
<li>
Exit psql:
<pre>postgres=# \q</pre>
</li>
</ul>
<h4 id="django-postgres">Configure Django to use Postgres</h4>
<p>
(Still in the <code>travelog-api</code> directory with virtualenv activated)
</p>
<ul>
<li>
Install <code>psycopg2</code> 2.7.7:
<pre class="console">$ pip install psycopg2-binary</pre>
</li>
<li>
Edit <code>travelog-api/travelog_api/settings.py</code>:
<pre class="python">
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql_psycopg2",
"NAME": "travelog",
"USER": "traveloguser",
"PASSWORD": "mypassword,
"HOST": "localhost",
"PORT": "54321",
}
}</pre
>
</li>
<li>
Run database migrations and run the server:
<pre class="console">
$ ./manage.py migrate
$ ./manage.py runserver
$ # go to http://localhost:8000 in the browser</pre
>
</li>
</ul>
<h4 id="graphene">Install and configure Graphene</h4>
<p>
(Still in the <code>travelog-api</code> directory with virtualenv activated)
</p>
<ul>
<li>
Install graphene-django 2.2.0
<pre class="console">$ pip install graphene-django</pre>
</li>
<li>
Edit the <code>INSTALLED_APPS</code> setting in
<code>travelog-api/travelog_api/settings.py</code>:
<pre class="python">
INSTALLED_APPS = (
# After the default packages
"graphene_django",
)</pre
>
</li>
</ul>
<h4 id="django-app">Create a new Django app and add a model</h4>
<p>
(Still in the <code>travelog-api</code> directory with virtualenv activated)
</p>
<ul>
<li>
Create a new Django app:
<pre class="console">$ ./manage.py startapp geo</pre>
</li>
<li>
Edit <code>travelog_api/settings.py</code>:
<pre class="python">
INSTALLED_APPS = (
# After the default packages
'graphene_django',
'geo',
)</pre
>
</li>
<li>
Edit <code>travelog-api/geo/models.py</code>:
<pre class="python">
from django.db.models import DateTimeField, FloatField, Model, TextField
class Location(Model):
created_at = DateTimeField(auto_now_add=True)
lat = FloatField()
lon = FloatField()
name = TextField(blank=True)
updated_at = DateTimeField(auto_now=True)</pre
>
</li>
<li>
Make and run migrations:
<pre class="console">
$ ./manage.py makemigrations
$ ./manage.py migrate</pre
>
</li>
</ul>
<h4 id="graphql">GraphQL all the things</h4>
<p>
(Still in the <code>travelog-api</code> directory with virtualenv activated)
</p>
<ul>
<li>
Create a new file <code>travelog-api/geo/schema.py</code>:
<pre class="python">
import graphene
from graphene_django.types import DjangoObjectType
from .models import Location
class LocationType(DjangoObjectType):
class Meta:
model = Location
class Query(object):
all_locations = graphene.List(LocationType)
def resolve_all_locations(self, info, **kwargs):
return Location.objects.all()
class CreateLocation(graphene.Mutation):
location = graphene.Field(LocationType)
class Arguments:
lat = graphene.Float()
lon = graphene.Float()
name = graphene.String()
def mutate(self, info, lat, lon, name):
loc = Location(lat=lat, lon=lon, name=name)
loc.save()
return CreateLocation(location=loc)
class Mutation(graphene.ObjectType):
create_location = CreateLocation.Field()</pre
>
</li>
<li>
Create a new file <code>travelog-api/travelog_api/schema.py</code>:
<pre class="python">
import graphene
import geo.schema
class Query(geo.schema.Query, graphene.ObjectType):
pass
class Mutation(geo.schema.Mutation, graphene.ObjectType):
pass
schema = graphene.Schema(query=Query, mutation=Mutation)</pre
>
</li>
<li>
Edit <code>travelog-api/travelog_api/urls.py</code>:
<pre class="python">
from django.contrib import admin
from django.urls import path
from graphene_django.views import GraphQLView
from .schema import schema
urlpatterns = [
path("admin/", admin.site.urls),
path("graphql/", GraphQLView.as_view(graphiql=True, schema=schema)),
]</pre
>
</li>
</ul>
<h4 id="try">Try it using the GraphiQL explorer</h4>
<p>
(Still in the <code>travelog-api</code> directory with virtualenv activated)
</p>
<ul>
<li>
Run the server:
<pre class="console">$ ./manage.py runserver</pre>
</li>
<li>
Go to
<a href="http://localhost:8000/graphql/"> http://localhost:8000/graphql/</a>
and you should see the
<a href="https://github.com/graphql/graphiql">GraphiQL</a> interactive
GraphQL explorer.
</li>
<li>
Create a location. Enter this mutation in the left pane and hit CTRL+ENTER:
<pre>
mutation {
createLocation(name: "my first location", lat: 1, lon: 2) {
location {
id
}
}
}</pre
>
See the response:
<pre>
{
"data": {
"createLocation": {
"location": {
"id": "1"
}
}
}
}</pre
>
</li>
<li>
Query all locations:
<pre>
query {
allLocations {
createdAt
id
lat
lon
name
}
}</pre
>
See the response:
<pre>
{
"data": {
"allLocations": [
{
"createdAt": "2019-02-22T06:39:08.512197+00:00",
"id": "1",
"lat": 1,
"lon": 2,
"name": "my first location"
}
]
}
}</pre
>
</li>
</ul>
<h4 id="references">References / See also</h4>
<ul>
<li>
<a href="https://www.howtographql.com/graphql-python/0-introduction/">
graphql-python Tutorial - How to GraphQL
</a>
</li>
<li>
<a href="https://docs.graphene-python.org/en/latest/"
>Graphene documentation</a
>
</li>
<li>
<a href="http://docs.graphene-python.org/projects/django/en/latest/"
>Graphene-Django documentation</a
>
</li>
<li>
<a
href="https://www.digitalocean.com/community/tutorials/how-to-use-postgresql-with-your-django-application-on-ubuntu-14-04"
>
https://www.digitalocean.com/community/tutorials/how-to-use-postgresql-with-your-django-application-on-ubuntu-14-04
</a>
</li>
<li>
<a href="https://www.postgresql.org/docs/11/static/sql-createuser.html">
https://www.postgresql.org/docs/11/static/sql-createuser.html
</a>
</li>
<li>
<a href="https://www.postgresql.org/docs/10/static/sql-createrole.html">
https://www.postgresql.org/docs/10/static/sql-createrole.html
</a>
</li>
<li>
<a href="https://hub.docker.com/_/postgres/">
https://hub.docker.com/_/postgres/
</a>
</li>
<li>
<a href="/blog/2019/01/how-run-postgresql-docker-mac-local-development/">
How to run PostgreSQL in Docker on Mac (for local development)</a
>
</li>
<li>
<a href="https://www.saltycrane.com/blog/2017/08/docker-cheat-sheet/">
Docker cheat sheet
</a>
</li>
</ul>
How to run a Django local development server on a remote machine and access it in your browser on your local machine using SSH port forwarding
2012-10-23T18:11:59-07:00https://www.saltycrane.com/blog/2012/10/how-run-django-local-development-server-remote-machine-and-access-it-your-browser-your-local-machine-using-ssh-port-forwarding/<p>Here is how to run a Django local development server on a remote machine and access it in your browser on your local machine using SSH port forwarding. (This is useful if there is a firewall blocking access to the port of your Django local dev server (port 8000).</p>
<ol>
<li>On the local host, SSH to the remote host:
<pre class="console">$ ssh -v -L 9000:localhost:8000 eliot@my.remotehost.com </pre>
</li>
<li>On the remote host, run the Django dev server:
<pre class="console">eliot@my.remotehost.com:/path/to/my/django/project$ python manage.py runserver 0.0.0.0:8000 </pre>
</li>
<li>On the local host, go to <a href="http://localhost:9000">http://localhost:9000</a> in the browser</li>
</ol>
<p>Note: The local port and the remote port can be the same (i.e. you can use 8000 instead of 9000). I just made them different to show which port is which.</p>
<h4>Using LocalForward in your ~/.ssh/config</h4>
<p>You can also achieve the same results by using the <code>LocalForward</code> in your <code>~/.ssh/config</code> file:</p>
<pre>Host myremote
User eliot
HostName my.remotehost.com
LocalForward 9000 localhost:8000</pre>
<h4>Reference</h4>
<p><a href="http://magazine.redhat.com/2007/11/06/ssh-port-forwarding/">http://magazine.redhat.com/2007/11/06/ssh-port-forwarding/</a></p>
Notes on tracing code execution in Django and Python
2011-06-25T07:29:00-07:00https://www.saltycrane.com/blog/2011/06/notes-tracing-code-execution-django-python/<p>
The <a href="http://docs.python.org/library/trace.html">trace</a> module causes Python
to print lines of code as they are executed.
I learned about trace via @brandon_rhodes's
<a href="http://twitter.com/#!/brandon_rhodes/status/81332578283552768">tweet</a>.
</p>
<h4>Trace a Python program</h4>
<pre>python -m trace -t myprogram.py myargs </pre>
<h4>Trace with a Django development server</h4>
<p>From my experience, trace doesn't work with Django's auto-reloader.
Use --noreload option</p>
<pre>python -m trace -t manage.py runserver --noreload </pre>
<h4>Tracing with more control</h4>
<p>
<a href="http://www.dalkescientific.com/writings/diary/archive/2005/04/20/tracing_python_code.html">
This article</a>
shows how to write custom functions that are passed to
<a href="http://docs.python.org/library/sys.html#sys.settrace"><code>sys.settrace</code></a>.
</p>
<h4>Django trace tool, <code>django-trace</code></h4>
<p>I wrote a Django management command that uses <code>sys.settrace</code> with other
Django management commands. <a href="https://github.com/saltycrane/django-trace">
https://github.com/saltycrane/django-trace</a>.
</p>
<p>Install</p>
<ul>
<li><pre>$ pip install -e git://github.com/saltycrane/django-trace.git#egg=django_trace </pre></li>
<li>Add <code>'django_trace'</code> to <code>INSTALLED_APPS</code> in <code>settings.py</code></li>
</ul>
<p>Usage</p>
<pre class="console">$ python manage.py trace --help
Usage: manage.py trace [options] [command]
Use sys.settrace to trace code
Options:
-v VERBOSITY, --verbosity=VERBOSITY
Verbosity level; 0=minimal output, 1=normal output,
2=verbose output, 3=very verbose output
--settings=SETTINGS The Python path to a settings module, e.g.
"myproject.settings.main". If this isn't provided, the
DJANGO_SETTINGS_MODULE environment variable will be
used.
--pythonpath=PYTHONPATH
A directory to add to the Python path, e.g.
"/home/djangoprojects/myproject".
--traceback Print traceback on exception
--include-builtins Include builtin functions (default=False)
--include-stdlib Include standard library modules (default=False)
--module-only Display module names only (not lines of code)
--calls-only Display function calls only (not lines of code)
--good=GOOD Comma separated list of exact module names to match
--bad=BAD Comma separated list of exact module names to exclude
(takes precedence over --good and --good-regex)
--good-regex=GOOD_REGEX
Regular expression of module to match
--bad-regex=BAD_REGEX
Regular expression of module to exclude (takes
precedence over --good and --good-regex)
--good-preset=GOOD_PRESET
A key in the GOOD_PRESETS setting
--bad-preset=BAD_PRESET
A key in the BAD_PRESETS setting
--version show program's version number and exit
-h, --help show this help message and exit</pre>
<p>or</p>
<pre class="console">$ python manage.py trace runserver
01->django.core.management:128: try:
01->django.core.management:129: app_name = get_commands()[name]
02-->django.core.management:95: if _commands is None:
02-->django.core.management:114: return _commands
01->django.core.management:130: if isinstance(app_name, BaseCommand):
01->django.core.management:134: klass = load_command_class(app_name, name)
02-->django.core.management:69: module = import_module('%s.management.commands.%s' % (app_name, name))
03--->django.utils.importlib:26: if name.startswith('.'):
03--->django.utils.importlib:35: __import__(name)
04---->django.contrib.staticfiles.management.commands.runserver:1: from optparse import make_option
04---->django.contrib.staticfiles.management.commands.runserver:3: from django.conf import settings
04---->django.contrib.staticfiles.management.commands.runserver:4: from django.core.management.commands.runserver import BaseRunserverCommand
05----->django.core.management.commands.runserver:1: from optparse import make_option
05----->django.core.management.commands.runserver:2: import os
05----->django.core.management.commands.runserver:3: import re
05----->django.core.management.commands.runserver:4: import sys
05----->django.core.management.commands.runserver:5: import socket
05----->django.core.management.commands.runserver:7: from django.core.management.base import BaseCommand, CommandError
05----->django.core.management.commands.runserver:8: from django.core.servers.basehttp import AdminMediaHandler, run, WSGIServerException, get_internal_wsgi_application
06------>django.core.servers.basehttp:8: """
06------>django.core.servers.basehttp:10: import os
06------>django.core.servers.basehttp:11: import socket
06------>django.core.servers.basehttp:12: import sys
06------>django.core.servers.basehttp:13: import traceback
...</pre>
<p>or</p>
<pre class="console">$ python manage.py trace --bad=django,SocketServer --calls-only runserver
01->wsgiref:23: """
01->wsgiref.simple_server:11: """
02-->BaseHTTPServer:18: """
03--->BaseHTTPServer:102: class HTTPServer(SocketServer.TCPServer):
03--->BaseHTTPServer:114: class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler):
02-->wsgiref.handlers:1: """Base classes for server/gateway implementations"""
03--->wsgiref.util:1: """Miscellaneous WSGI-related Utilities"""
04---->wsgiref.util:11: class FileWrapper:
03--->wsgiref.headers:6: """
04---->wsgiref.headers:42: class Headers:
03--->wsgiref.handlers:43: class BaseHandler:
03--->wsgiref.handlers:371: class SimpleHandler(BaseHandler):
03--->wsgiref.handlers:412: class BaseCGIHandler(SimpleHandler):
03--->wsgiref.handlers:453: class CGIHandler(BaseCGIHandler):
02-->wsgiref.simple_server:26: class ServerHandler(SimpleHandler):
02-->wsgiref.simple_server:42: class WSGIServer(HTTPServer):
02-->wsgiref.simple_server:83: class WSGIRequestHandler(BaseHTTPRequestHandler):
01->contextlib:53: def contextmanager(func):
01->contextlib:53: def contextmanager(func):
Validating models...
0 errors found
Django version 1.4, using settings 'myproj.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
01->myproj.wsgi:15: """
01->wsgiref.simple_server:48: def server_bind(self):
02-->BaseHTTPServer:106: def server_bind(self):
02-->wsgiref.simple_server:53: def setup_environ(self):
01->wsgiref.simple_server:53: def setup_environ(self):
01->wsgiref.simple_server:66: def set_app(self,application)</pre>
<h4 id="trace-decorator">Trace decorator</h4>
<p>I also wrote a decorator that traces code execution of the function
it is decorating: <a href="https://github.com/saltycrane/trace-tools">https://github.com/saltycrane/trace-tools</a>
</p>
<p>Install</p>
<pre>pip install -e git://github.com/saltycrane/trace-tools.git#egg=trace_tools </pre>
<p>Usage</p>
<pre class="python">from trace_tools.decorators import trace
@trace()
def some_function_to_trace(arg):
do_something()
@trace(max_level=2)
def some_function_to_trace(arg):
do_something()
@another_decorator
@trace(
max_level=4,
ignore=(
'httplib', 'logging', 'ssl', 'email', 'encodings', 'gzip', 'urllib',
'multiprocessing', 'django', 'cgi', 'requests', 'cookielib', 'base64',
'slumber', 'zipfile', 'redis'))
def some_other_function():
do_something_else()
@trace(max_level=10, calls_only=False, ignore=('debugtools', 'blessings', 'ipdb', 'IPython',), ignore_builtins=True, ignore_stdlib=True)
def process(self, content):
do_stuff()
@trace(max_level=115, calls_only=True, ignore=(
'suds.resolver',
'suds.sudsobject',
'suds.xsd',
'debugtools', 'blessings', 'ipdb', 'IPython',),
ignore_builtins=True, ignore_stdlib=True)
def process(self, content):
do_stuff()</pre>
psycopg2 "could not connect to server" error on Ubuntu
2010-06-08T21:48:24-07:00https://www.saltycrane.com/blog/2010/06/psycopg2-could-not-connect-server-error-ubuntu/<p>While trying to run <code>python manage.py syncdb</code> with
Django 1.2.1, psycopg2 2.2.1 and PostgreSQL 8.4 on Ubuntu Lucid,
I got the following error saying I couldn't connect to the database.
</p>
<pre class="console">$ python manage.py syncdb
Traceback (most recent call last):
File "manage.py", line 11, in <module>
execute_manager(settings)
File "/home/eliot/lib/python-environments/saltycrane121/lib/python2.6/site-packages/django/core/management/__init__.py", line 438, in execute_manager
utility.execute()
File "/home/eliot/lib/python-environments/saltycrane121/lib/python2.6/site-packages/django/core/management/__init__.py", line 379, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/home/eliot/lib/python-environments/saltycrane121/lib/python2.6/site-packages/django/core/management/base.py", line 191, in run_from_argv
self.execute(*args, **options.__dict__)
File "/home/eliot/lib/python-environments/saltycrane121/lib/python2.6/site-packages/django/core/management/base.py", line 218, in execute
output = self.handle(*args, **options)
File "/home/eliot/lib/python-environments/saltycrane121/lib/python2.6/site-packages/django/core/management/base.py", line 347, in handle
return self.handle_noargs(**options)
File "/home/eliot/lib/python-environments/saltycrane121/lib/python2.6/site-packages/django/core/management/commands/syncdb.py", line 52, in handle_noargs
cursor = connection.cursor()
File "/home/eliot/lib/python-environments/saltycrane121/lib/python2.6/site-packages/django/db/backends/__init__.py", line 75, in cursor
cursor = self._cursor()
File "/home/eliot/lib/python-environments/saltycrane121/lib/python2.6/site-packages/django/db/backends/postgresql_psycopg2/base.py", line 136, in _cursor
self.connection = Database.connect(**conn_params)
psycopg2.OperationalError: could not connect to server: No such file or directory
Is the server running locally and accepting
connections on Unix domain socket "/var/run/postgresql/.s.PGSQL.5432"?
</pre>
<p>I tried connecting via <code>psql</code> and that worked. I looked in
<code>/var/run/postgresql/</code> and found:
</p>
<pre class="console">$ ls -lA /var/run/postgresql
total 8
srwxrwxrwx 1 postgres postgres 0 2010 06/08 21:29 .s.PGSQL.5433
-rw------- 1 postgres postgres 34 2010 06/08 21:29 .s.PGSQL.5433.lock
-rw------- 1 postgres postgres 5 2010 06/08 21:29 8.4-main.pid</pre>
<p>So it turns out my Postgres server is running on port 5433, but
Django (psycopg2) expects to find it at 5432 by default.
So I changed the port number
to 5433 in my <code>settings.py</code> and it worked.
</p>
<p>Note: I looks like the reason my Postgres server was running on port
5433 instead of 5432 is because I previously had PostgreSQL 8.3 installed
on my machine and there were configuration files left in
<code>/etc/postgresql/8.3/</code>
The port setting is configured in <code>/etc/postgresql/8.x/main/postgresql.conf</code>.
</p>
Adding a per-post comments feed with Django 1.0
2010-03-27T15:06:07-07:00https://www.saltycrane.com/blog/2010/03/adding-post-comments-feed-django-10/<p>I've added an Atom feed for comments on a single post on this blog
(per a <a href="/blog/2008/02/backup-on-linux-rsnapshot-vs-rdiff/#c8451">
request in the comments</a>).
Here are my notes. I am using Django 1.0. (<em>Note: the Django feeds framework
has changed for 1.2. See the Django 1.2
<a href="http://docs.djangoproject.com/en/dev/releases/1.2/#updating-feeds">release
notes</a> for more information.</em>)
</p>
<p>Added to <code>/srv/SaltyCrane/iwiwdsmi/feeds.py</code>:</p>
<pre class="python">from django.contrib.comments.models import Comment
from django.contrib.syndication.feeds import Feed, FeedDoesNotExist
from django.utils.feedgenerator import Atom1Feed
from django.core.exceptions import ObjectDoesNotExist
from iwiwdsmi.myblogapp.models import Post
from iwiwdsmi.settings import SITE_NAME
class CommentsByPost(Feed):
title_template = "feeds/comments_title.html"
description_template = "feeds/comments_description.html"
feed_type = Atom1Feed
def get_object(self, bits):
if len(bits) != 1:
raise ObjectDoesNotExist
return Post.objects.get(id=bits[0])
def title(self, obj):
return '%s' % obj
def description(self, obj):
return 'Comments on "%s": %s' % (obj, SITE_NAME)
def link(self, obj):
if not obj:
raise FeedDoesNotExist
return obj.get_absolute_url() + "#comments"
def items(self, obj):
return Comment.objects.filter(object_pk=str(obj.id),
is_public=True,
is_removed=False,
).order_by('-submit_date')
def item_pubdate(self, item):
return item.submit_date</pre>
<p>Added to <code>/srv/SaltyCrane/iwiwdsmi/urls.py</code>:</p>
<pre class="python">from iwiwdsmi.feeds import CommentsByPost
feeds = {
'comments': CommentsByPost,
}
urlpatterns = patterns(
'',
(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed',
{'feed_dict': feeds}),
)</pre>
<p>Added <code>/srv/SaltyCrane/iwiwdsmi/templates/feeds/comments_title.html</code>:</p>
<pre class="django">Comment by {{ obj.name|escape }}</pre>
<p>Added <code>/srv/SaltyCrane/iwiwdsmi/templates/feeds/comments_description.html</code>:</p>
<pre class="django">{% load markup %}
{{ obj.comment|markdown:"safe" }}</pre>
<p>Added to <code>/srv/SaltyCrane/iwiwdsmi/templates/myblogapp/singlepost.html</code>:</p>
<pre class="django"><a href="/feeds/comments/{{ post.id }}/">
<img src="http://saltycrane.s3.amazonaws.com/image/icon_feed_orange_14x14_1.png" style="border: 0pt none ; vertical-align: middle;" alt="feed icon">
Comments feed for this post</a></pre>
Notes on switching my Djangos to mod_wsgi
2009-10-22T00:44:17-07:00https://www.saltycrane.com/blog/2009/10/notes-switching-my-djangos-mod_wsgi/<p>I'm slowly trying to make my Django web servers conform to current
best practices. I've
<a href="/blog/2009/04/notes-using-nginx-mod_python-and-django/">
set up an Nginx reverse proxy for serving static files</a>,
<a href="/blog/2009/05/notes-using-pip-and-virtualenv-django/">
started using virtualenv to isolate my Python environments</a>, and
<a href="/blog/2009/08/notes-migrating-blog-sqlite-postgresql/">
migrated my database to PostgreSQL</a>. I ultimately want to implement
<a href="http://bretthoerner.com/blog/2008/oct/27/using-nginx-memcached-module-django/">
memcached+Nginx caching</a> in my reverse proxy, but the next task on my to-do list
is switching from mod_python to <a href="http://code.google.com/p/modwsgi/">mod_wsgi</a>.
</p>
<p>
Within the past year (or maybe before), mod_wsgi has become the
<a href="http://docs.djangoproject.com/en/dev/howto/deployment/modwsgi/">
preferred</a>
<a href="http://simonwillison.net/2009/Apr/1/modwsgi/">method</a>
for serving Django applications. I also originally thought
<a href="http://collingrady.wordpress.com/2009/01/06/mod_python-versus-mod_wsgi/">
switching
from mod_python</a> to mod_wsgi would save me some much needed memory on my
256MB VPS. But after trying it out, running with a single Apache process
in each case, the memory footprint was about the same. Even switching
from mod_wsgi's embedded mode to daemon mode didn't make a significant difference.
Likely the performance is better with mod_wsgi, though.
</p>
<p>Here are my notes on installing mod_wsgi.</p>
<h4>Configuration References</h4>
<ul>
<li>Django docs: <a href="http://docs.djangoproject.com/en/dev/howto/deployment/modwsgi/#howto-deployment-modwsgi">
How to use Django with Apache and mod_wsgi</a></li>
<li>mod_wsgi docs: <a href="http://code.google.com/p/modwsgi/wiki/QuickConfigurationGuide">
Quick Configuration Guide</a></li>
<li>A good blog article: <a href="http://bretthoerner.com/blog/2008/oct/09/configs-nginx-and-apache-mod_wsgi/">
Configs for nginx and Apache with mod_wsgi</a></li>
</ul>
<h4>Advice from mod_wsgi author Graham Dumpleton</h4>
<ul>
<li><a href="http://blog.dscpl.com.au/2009/03/load-spikes-and-excessive-memory-usage.html">
Load spikes and excessive memory usage in mod_python</a></li>
<li><a href="http://blog.dscpl.com.au/2008/12/using-modwsgi-when-developing-django.html">
Using mod_wsgi when developing Django sites</a></li>
<li><a href="http://groups.google.com/group/django-users/browse_thread/thread/6d670b0fa7c0d733/4ff111a1f00f7629?q=worker+daemon#4ff111a1f00f7629">
http://groups.google.com/group/django-users/browse_thread/thread/6d670b0fa7c0d733/4ff111a1f00f7629?q=worker+daemon#4ff111a1f00f7629</a></li>
<li><a href="http://groups.google.com/group/modwsgi/browse_thread/thread/cb21864a97d44ee9/38716433921a48cb?q=worker+daemon#38716433921a48cb">
http://groups.google.com/group/modwsgi/browse_thread/thread/cb21864a97d44ee9/38716433921a48cb?q=worker+daemon#38716433921a48cb</a></li>
</ul>
<h4>Install mod_wsgi and apache mpm-worker</h4>
<p>I'm not 100% sure about prefork vs. worker mpm, but Graham Dumpleton
favors worker mpm.</p>
<pre>sudo apt-get install libapache2-mod-wsgi
sudo apt-get install apache2-mpm-worker</pre>
<h4>Create .wsgi application file</h4>
<p>My virtualenv is located at <code>/srv/python-environments/saltycrane</code>.
My Django settings files is at <code>/srv/SaltyCrane/iwiwdsmi/settings.py</code>.
</p>
<p><code>/srv/SaltyCrane/saltycrane.wsgi</code>:</p>
<pre class="python">import os
import sys
import site
site.addsitedir('/srv/python-environments/saltycrane/lib/python2.5/site-packages')
os.environ['DJANGO_SETTINGS_MODULE'] = 'iwiwdsmi.settings'
sys.path.append('/srv/SaltyCrane')
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()</pre>
<h4>Edit Apache's httpd.conf file</h4>
<p>I went back and forth between using embedded mode or daemon mode.
I've ended up with embedded mode for now since it seems to use a tad less
memory and is supposed to be a little bit faster. However, Graham Dumpleton seems to
recommend daemon mode for people on VPSs. I may change my mind again later.
To use daemon mode, I just need to uncomment the <code>WSGIDaemonProcess</code>
and <code>WSGIProcessGroup</code> lines.
I have <code>StartServers</code> set to 1 because I can only afford to have
one Apache process running.
This is assuming nginx is proxying requests to apache. For more
on my nginx setup, see <a href="/blog/2009/04/notes-using-nginx-mod_python-and-django/">
here</a>.</p>
<p>Edit <code>/etc/apache2/httpd.conf</code>:</p>
<pre>
<IfModule mpm_worker_module>
StartServers 1
ServerLimit 1
ThreadsPerChild 5
ThreadLimit 5
MinSpareThreads 5
MaxSpareThreads 5
MaxClients 5
MaxRequestsPerChild 500
</IfModule>
KeepAlive Off
NameVirtualHost 127.0.0.1:8080
Listen 8080
<VirtualHost 127.0.0.1:8080>
ServerName www.saltycrane.com
# WSGIDaemonProcess saltycrane.com processes=1 threads=5 display-name=%{GROUP}
# WSGIProcessGroup saltycrane.com
WSGIScriptAlias / /srv/SaltyCrane/saltycrane.wsgi
</VirtualHost>
<VirtualHost 127.0.0.1:8080>
ServerName supafu.com
# WSGIDaemonProcess supafu.com processes=1 threads=5 display-name=%{GROUP}
# WSGIProcessGroup supafu.com
WSGIScriptAlias / /srv/Supafu/supafu.wsgi
</VirtualHost>
<VirtualHost 127.0.0.1:8080>
ServerName handsoncards.com
# WSGIDaemonProcess handsoncards.com processes=1 threads=5 display-name=%{GROUP}
# WSGIProcessGroup handsoncards.com
WSGIScriptAlias / /srv/HandsOnCards/handsoncards.wsgi
</VirtualHost>
</pre>
<h4>Restart Apache</h4>
<pre>sudo /etc/init.d/apache2 restart</pre>
Notes on migrating this blog from SQLite to PostgreSQL using Django
2009-08-07T23:54:14-07:00https://www.saltycrane.com/blog/2009/08/notes-migrating-blog-sqlite-postgresql/<p>Here are my notes on migrating this blog from SQLite to PostgreSQL. For the parts
of my database that were in proper order, the migration was made very easy using
Django's
<a href="http://docs.djangoproject.com/en/dev/ref/django-admin/#dumpdata">./manage.py dumpdata</a>
and
<a href="http://docs.djangoproject.com/en/dev/ref/django-admin/#loaddata-fixture-fixture">
./manage.py loaddata</a> commands. However, the database tables used for storing
the comments on this blog were kind of screwed up because I had
<a href="/blog/2008/07/django-blog-project-9-migrating-blogger/">
previously migrated</a> them from my old Blogger blog. So I had to write
another (not so pretty) script for that.
</p>
<p>Thanks to
<a href="http://stevelord.tumblr.com/post/86674737/migrating-an-sqlite-based-django-app-to-postgresql">
this article</a> for showing me how to use Django's dumpdata and loaddata for
this migration.
</p>
<h4>Create a new Postgres database</h4>
<p>See <a href="/blog/2008/12/card-store-project-2-installing-satchmo-django-postgresql-and-apache-ubuntu-slicehost/#postgres">
my previous notes</a> for creating a Postgres database. I named the new
database "saltycrane_db" owned by "django_user" with password "my_password".
</p>
<h4>Clone and modify Django project</h4>
<ul>
<li>Clone my SaltyCrane project
<pre>hg clone SaltyCrane SaltyCraneNew</pre>
or
<pre>cp -rp SaltyCrane SaltyCraneNew</pre>
</li>
<li>Edit settings.py:
<pre>DATABASE_ENGINE = 'postgresql_psycopg2'
DATABASE_NAME = 'saltycrane_db'
DATABASE_USER = 'django_user'
DATABASE_PASSWORD = my_password'</pre>
</li>
<li>Create database tables
<pre>python manage.py syncdb</pre>
</li>
</ul>
<h4>Migrate data</h4>
<ul>
<li>Create JSON dumps from the existing SQLite database for my 3 Django apps (myblogapp, comments, and tagging):
<pre>cd /srv/SaltyCrane/iwiwdsmi
./manage.py dumpdata myblogapp > dump_myblogapp.json
./manage.py dumpdata comments > dump_comments.json
./manage.py dumpdata tagging > dump_tagging.json</pre>
</li>
<li>Load the JSON data dumps to the new Postgres database:
<pre>cd /srv/SaltyCraneNew/iwiwdsmi
./manage.py loaddata ../../SaltyCrane/iwiwdsmi/dump_tagging.json
./manage.py loaddata ../../SaltyCrane/iwiwdsmi/dump_myblogapp.json</pre>
</li>
</ul>
<p>However, loading comments didn't work because I had some missing fields so...</p>
<h4>Migration script</h4>
<p>...I wrote a migration script:</p>
<pre class="python">import setup_environment
import simplejson
from pprint import pprint
from django.contrib.comments.models import Comment
from django.contrib.contenttypes.models import ContentType
from iwiwdsmi.myblogapp.models import Post
JSON_FILENAME = "/srv/SaltyCrane/iwiwdsmi/dump_comments.json"
ct = ContentType.objects.get(name='post', app_label='myblogapp')
N_OLD_COMMENTS = 4000
def main():
delete_all_comments()
create_dummy_comments()
pydata = open_json_file(JSON_FILENAME)
save_items_to_database(pydata)
delete_dummy_comments()
def delete_all_comments():
""" Clears the database of all comments.
"""
comments = Comment.objects.all()
comments.delete()
print "All comments deleted."
def create_dummy_comments():
""" Create a bunch of filler dummy comments
"""
for i in range(N_OLD_COMMENTS):
c = Comment()
c.comment = "Filler comment."
c.content_type = ct
c.ip_address = None
c.is_public = False
c.is_removed = False
c.object_pk = 243
c.site_id = 1
c.user_email = "filler@email.com"
c.user_name = "Filler"
c.save()
print "Filler comments created."
def delete_dummy_comments():
comments = Comment.objects.filter(is_public=False)
comments.delete()
print "Dummy comments deleted."
def open_json_file(filename):
""" open the json file and return the python data structure
"""
json_fp = open(filename)
pydata = simplejson.load(json_fp)
return pydata
def save_items_to_database(pydata):
""" Process the python data structure and save to the new database
"""
for item in pydata:
pprint(item)
pk = item['pk']
item = item['fields']
if item['is_public'] and not item['is_removed']:
c = Comment.objects.get(id=pk)
c.comment = item['comment']
c.content_type = ct
c.ip_address = get_ip_address(item['ip_address'])
c.is_public = item['is_public']
c.is_removed = item['is_removed']
c.object_pk = item['object_pk']
c.site_id = 1
c.submit_date = item['submit_date']
c.user_id = item['user']
c.user_email = item['user_email']
c.user_name = item['user_name']
c.user_url = item['user_url']
c.save()
def get_ip_address(ip):
""" Handle bad input for IP addresses
"""
if ip == "" or ip == "unknown":
return None
else:
return ip
if __name__ == '__main__':
main()</pre>
Django notes to self
2009-08-05T22:43:19-07:00https://www.saltycrane.com/blog/2009/08/notetoself22/<h4>How to delete all tables associated with a Django app</h4>
<pre>./manage.py sqlclear myapp | ./manage.py dbshell</pre>
<p>via <a href="http://girasquid.com/2008/11/19/wiping-and-updating-your-database-with-django/">Luke Hutscal</a></p>
Postgres notes
2009-05-10T00:22:49-07:00https://www.saltycrane.com/blog/2009/05/notetoself10/<p>Here are some Postgres notes to myself.</p>
<h4>How to add a column to an existing table </h4>
<p>I'm going to add the "tags" column to the "product" table
</p>
<pre>cd /srv/HandsOnCards/handsoncards
python manage.py sqlall product</pre>
It returns (among other things):
<pre>CREATE TABLE "product_product" (
"id" serial NOT NULL PRIMARY KEY,
"site_id" integer NOT NULL REFERENCES "django_site" ("id") DEFERRABLE INITIALLY DEFERRED,
"name" varchar(255) NOT NULL,
"slug" varchar(80) NOT NULL,
"sku" varchar(255) NULL,
"short_description" text NOT NULL,
"description" text NOT NULL,
"items_in_stock" integer NOT NULL,
"meta" text NULL,
"date_added" date NULL,
"active" boolean NOT NULL,
"featured" boolean NOT NULL,
"ordering" integer NOT NULL,
"weight" numeric(8, 2) NULL,
"weight_units" varchar(3) NULL,
"length" numeric(6, 2) NULL,
"length_units" varchar(3) NULL,
"width" numeric(6, 2) NULL,
"width_units" varchar(3) NULL,
"height" numeric(6, 2) NULL,
"height_units" varchar(3) NULL,
"total_sold" integer NOT NULL,
"taxable" boolean NOT NULL,
"taxClass_id" integer NULL REFERENCES "tax_taxclass" ("id") DEFERRABLE INITIALLY DEFERRED,
"shipclass" varchar(10) NOT NULL,
"tags" varchar(255) NULL,
UNIQUE ("site_id", "sku"),
UNIQUE ("site_id", "slug")
)</pre>
<pre>su postgres
psql handsoncards_db</pre>
<pre>ALTER TABLE product_product ADD tags varchar(255) NULL;
\q</pre>
<pre>exit</pre>
<p>See the <a href="http://www.postgresql.org/docs/8.3/static/sql-altertable.html">
Postgresql ALTER TABLE documentation</a> for more information.
</p>
<h4>Create a new database</h4>
<ul>
<li>
Create a database named <code>django_db</code>.
Assume the user, <code>django_user</code>, has already been created.
<pre>su postgres
psql template1</pre>
<pre>CREATE DATABASE django_db OWNER django_user ENCODING 'UTF8';
\q</pre>
<pre>exit</pre>
</li>
<li>
Configure access to the database. Add the following line to
<code>/etc/postgresql/8.3/main/pg_hba.conf</code>:
<pre>local django_db django_user md5</pre>
</li>
<li>
Restart the postgres server:
<pre>sudo /etc/init.d/postgresql-8.3 restart</pre>
</li>
</ul>
<h4>Give a user the privilege to create databases</h4>
<ul>
<li><pre>su postgres</pre></li>
<li><pre>psql template1</pre></li>
<li>List users
<pre>\du</pre>
</li>
<li><pre>ALTER USER my_username WITH CREATEDB;</pre></li>
<li><pre>\q</pre></li>
<li><pre>exit</pre></li>
</ul>
<p>See <a href="http://www.postgresql.org/docs/8.4/static/sql-alteruser.html">
http://www.postgresql.org/docs/8.4/static/sql-alteruser.html</a></p>
Notes on using pip and virtualenv with Django
2009-05-06T23:18:37-07:00https://www.saltycrane.com/blog/2009/05/notes-using-pip-and-virtualenv-django/<p>I have been using
<a href="/blog/2008/08/somewhere-your-python-path/">
a symlinking method</a> to install Python packages up to this point. To better
handle dependencies and multiple versions I have wanted to switch over to
<a href="http://pypi.python.org/pypi/pip">pip</a> and
<a href="http://pypi.python.org/pypi/virtualenv">virtualenv</a>.
Pip is a better alternative to
<a href="http://peak.telecommunity.com/DevCenter/EasyInstall">Easy Install</a>
and virtualenv is a tool to create isolated Python environments.
I have wanted to use pip and virtualenv for a long time now. Finally, today,
I took my first steps and created an environment with the Python packages
required for this blog. My notes are below. (I am running Ubuntu
<del>Intrepid</del><del>Karmic</del>Maverick and Python <del>2.5</del>2.6.)
A lot of my notes on virtualenv are taken from
<a href="http://arthurkoziel.com/2008/10/22/working-virtualenv/">
Arthur Koziel's excellent tutorial</a>.
</p>
<p><em>Update 2012-03-14:</em> Updated examples for pip 1.1 and virtualenv 1.7. As of pip 1.1,
the <code>-E</code> option is removed. As of virtualenv 1.7, the <code>--no-site-packages</code>
has become the default and is deprecated. Use <code>--system-site-packages</code> if you
want to include system site packages (the old default behavior). Examples are run on Ubuntu
10.10 Maverick Meerkat.
</p>
<h4 id="install-easy-install">Install Easy Install</h4>
<p>If you don't already have Easy Install, it can be installed as follows:</p>
<pre class="console">$ sudo apt-get install python-setuptools python-dev build-essential</pre>
<h4 id="install-pip">Install pip</h4>
<p>In most cases it is not necessary to install pip because it is included with virtualenv.</p>
<pre class="console">$ sudo easy_install -U pip
install_dir /usr/local/lib/python2.6/dist-packages/
Searching for pip
Reading http://pypi.python.org/simple/pip/
Reading http://pip.openplans.org
Reading http://www.pip-installer.org
Best match: pip 1.1
Downloading http://pypi.python.org/packages/source/p/pip/pip-1.1.tar.gz#md5=62a9f08dd5dc69d76734568a6c040508
Processing pip-1.1.tar.gz
Running pip-1.1/setup.py -q bdist_egg --dist-dir /tmp/easy_install-i_5nEU/pip-1.1/egg-dist-tmp-ytHsCZ
warning: no files found matching '*.html' under directory 'docs'
warning: no previously-included files matching '*.txt' found under directory 'docs/_build'
no previously-included directories found matching 'docs/_build/_sources'
Adding pip 1.1 to easy-install.pth file
Installing pip script to /usr/local/bin
Installing pip-2.6 script to /usr/local/bin
Installed /usr/local/lib/python2.6/dist-packages/pip-1.1-py2.6.egg
Processing dependencies for pip
Finished processing dependencies for pip</pre>
<h4 id="install-virtualenv">Install virtualenv</h4>
<pre class="console">$ sudo easy_install -U virtualenv
install_dir /usr/local/lib/python2.6/dist-packages/
Searching for virtualenv
Reading http://pypi.python.org/simple/virtualenv/
Reading http://www.virtualenv.org
Reading http://virtualenv.openplans.org
Best match: virtualenv 1.7.1.2
Downloading http://pypi.python.org/packages/source/v/virtualenv/virtualenv-1.7.1.2.tar.gz#md5=3be8a014c27340f48b56465f9109d9fa
Processing virtualenv-1.7.1.2.tar.gz
Running virtualenv-1.7.1.2/setup.py -q bdist_egg --dist-dir /tmp/easy_install-td3AoM/virtualenv-1.7.1.2/egg-dist-tmp-7dJVfO
warning: no previously-included files matching '*.*' found under directory 'docs/_templates'
Adding virtualenv 1.7.1.2 to easy-install.pth file
Installing virtualenv script to /usr/local/bin
Installed /usr/local/lib/python2.6/dist-packages/virtualenv-1.7.1.2-py2.6.egg
Processing dependencies for virtualenv
Finished processing dependencies for virtualenv</pre>
<h4 id="create-virtualenv">Create a virtual environment</h4>
<p>This command creates the isolated virtual environment.</p>
<p><em>Update 2010-04-13:</em> Updated to use
<a href="http://pypi.python.org/pypi/distribute">distribute</a> because
it is the <a href="http://s3.pixane.com/pip_distribute.png">new hotness</a>.
</p>
<pre class="console">$ cd /tmp</pre>
<pre class="console">$ virtualenv --distribute myenv
New python executable in myenv/bin/python
Installing distribute.............................................................................................................................................................................................done.
Installing pip...............done.</pre>
<h4 id="create-virtualenv-with-a-different-version-of-python">Create a virtualenv with a different version of Python</h4>
<p>This creates a virtualenv that uses Python 2.7 instead of the default Python 2.6.</p>
<pre class="console">$ virtualenv --distribute --python=/usr/bin/python2.7 myenv-py27
Running virtualenv with interpreter /usr/bin/python2.7
New python executable in myenv-py27/bin/python2.7
Also creating executable in myenv-py27/bin/python
Installing distribute.............................................................................................................................................................................................done.
Installing pip...............done.</pre>
<h4 style="color:#888888" id="clear-pythonpath">Clear the PYTHONPATH variable</h4>
<p style="color:#888888">I don't know if this is necessary, but I had a problem with the akismet
module when this was set. Maybe I did something wrong, but when I cleared
PYTHONPATH, it worked.
</p>
<pre style="color:#888888">$ export PYTHONPATH=</pre>
<h4 id="install-yolk">Install a package (Yolk) in the new virtual environment</h4>
<p>Activate the virtual environment and install Yolk inside it.
(<a href="http://pypi.python.org/pypi/yolk">Yolk</a> is a tool that
lists Python packages.)
</p>
<pre class="console">$ source /tmp/myenv/bin/activate
$ pip install yolk
Downloading/unpacking yolk
Downloading yolk-0.4.3.tar.gz (86Kb): 86Kb downloaded
Running setup.py egg_info for package yolk
warning: no files found matching '*.txt' under directory 'tests'
warning: no files found matching '*.conf' under directory 'docs'
warning: no files found matching '*.css_t' under directory 'docs'
warning: no files found matching 'indexsidebar.html' under directory 'docs'
warning: no files found matching 'tests/test_cli.py'
Requirement already satisfied (use --upgrade to upgrade): distribute in ./myenv/lib/python2.6/site-packages/distribute-0.6.24-py2.6.egg (from yolk)
Installing collected packages: yolk
Running setup.py install for yolk
warning: no files found matching '*.txt' under directory 'tests'
warning: no files found matching '*.conf' under directory 'docs'
warning: no files found matching '*.css_t' under directory 'docs'
warning: no files found matching 'indexsidebar.html' under directory 'docs'
warning: no files found matching 'tests/test_cli.py'
Installing yolk script to /tmp/myenv/bin
Successfully installed yolk
Cleaning up...</pre>
<h4 id="activate-virtualenv">Use the virtual environment</h4>
<ul>
<li>Activate the virtual environment
<pre class="console">$ source /tmp/myenv/bin/activate</pre>
</li>
<li>Run yolk to list your installed packages:
<pre class="console">$ yolk -l
Python - 2.6.6 - active development (/usr/lib/python2.6/lib-dynload)
distribute - 0.6.24 - active
pip - 1.1 - active
wsgiref - 0.1.2 - active development (/usr/lib/python2.6)
yolk - 0.4.3 - active</pre>
</li>
<li>Deactivate the environment
<pre class="console">$ deactivate</pre>
</li>
<li>Try running yolk
<pre class="console">$ yolk -l
yolk: command not found</pre>
</li>
</ul>
<h4 id="install-django">Install Django in the virtual environment</h4>
<pre class="console">$ source /tmp/myenv/bin/activate
$ pip install Django
Downloading/unpacking Django
Downloading Django-1.3.1.tar.gz (6.5Mb): 6.5Mb downloaded
Running setup.py egg_info for package Django
Installing collected packages: Django
Running setup.py install for Django
changing mode of build/scripts-2.6/django-admin.py from 644 to 755
changing mode of /tmp/myenv/bin/django-admin.py to 755
Successfully installed Django
Cleaning up...</pre>
<h4 id="pip-freeze">Create a requirements file from existing library versions using pip freeze</h4>
<br>
<p>The <code>pip freeze</code> command allows you to take a snapshot of the
exact versions of all your Python libraries. For more information,
see the documentation on
<a href="http://www.pip-installer.org/en/latest/requirements.html#freezing-requirements">pip freeze</a>.
</p>
<pre class="console">$ source /tmp/myenv/bin/activate
$ pip freeze > /tmp/requirements.txt</pre>
<pre class="console">$ cat /tmp/requirements.txt
Django==1.3.1
distribute==0.6.24
wsgiref==0.1.2
yolk==0.4.3</pre>
<h4 id="pip-uninstall">Uninstall a package</h4>
<pre class="console">$ source /tmp/myenv/bin/activate
$ pip uninstall Django
Uninstalling Django:
/tmp/myenv/bin/django-admin.py
/tmp/myenv/lib/python2.6/site-packages/Django-1.3.1-py2.6.egg-info
/tmp/myenv/lib/python2.6/site-packages/django
Proceed (y/n)? y
Successfully uninstalled Django</pre>
<span id="requirements"></span>
<h4 id="pip-requirements">Install libraries based on a requirements file</h4>
<p>Once you have a requirements file, you can use pip to install the exact versions
of the libraries specified in your requirements file. For more information,
see the documentation on <a href="http://www.pip-installer.org/en/latest/requirements.html">pip
requirements files</a>.
Here's my requirements file for this blog,
<code>/tmp/saltycrane-requirements.txt</code>:</p>
<pre>psycopg2==2.4.2
Django==1.3.1
Markdown==2.0
http://www.crummy.com/software/BeautifulSoup/download/3.x/BeautifulSoup-3.0.7a.tar.gz
Pygments==1.3.1
Twisted==10.0.0
http://django-tagging.googlecode.com/files/django-tagging-0.3.1.tar.gz
-e hg+http://bitbucket.org/ubernostrum/django-contact-form/#egg=django-contact-form
-e hg+http://bitbucket.org/jezdez/akismet/#egg=akismet
Fabric==1.3.1</pre>
<p>Here's the command to install using my reqirements file:
</p>
<pre class="console" style="height: 600px; overflow: auto">$ source /tmp/myenv/bin/activate
$ pip install -r /tmp/saltycrane-requirements.txt
Downloading/unpacking http://www.crummy.com/software/BeautifulSoup/download/3.x/BeautifulSoup-3.0.7a.tar.gz (from -r /tmp/saltycrane-requirements.txt (line 7))
Downloading BeautifulSoup-3.0.7a.tar.gz
Running setup.py egg_info for package from http://www.crummy.com/software/BeautifulSoup/download/3.x/Beaut
ifulSoup-3.0.7a.tar.gz
Downloading/unpacking http://django-tagging.googlecode.com/files/django-tagging-0.3.1.tar.gz (from -r /tmp/saltycrane-requirements.txt (line 11))
Downloading django-tagging-0.3.1.tar.gz
Running setup.py egg_info for package from http://django-tagging.googlecode.com/files/django-tagging-0.3.1.tar.gz
Downloading/unpacking psycopg2==2.4.2 (from -r /tmp/saltycrane-requirements.txt (line 1))
Downloading psycopg2-2.4.2.tar.gz (667Kb): 667Kb downloaded
Running setup.py egg_info for package psycopg2
no previously-included directories found matching 'doc/src/_build'
Downloading/unpacking Django==1.3.1 (from -r /tmp/saltycrane-requirements.txt (line 4))
Downloading Django-1.3.1.tar.gz (6.5Mb): 6.5Mb downloaded
Running setup.py egg_info for package Django
Downloading/unpacking Markdown==2.0 (from -r /tmp/saltycrane-requirements.txt (line 5))
Downloading Markdown-2.0.zip (93Kb): 93Kb downloaded
Running setup.py egg_info for package Markdown
Downloading/unpacking Pygments==1.3.1 (from -r /tmp/saltycrane-requirements.txt (line 8))
Downloading Pygments-1.3.1.tar.gz (1.1Mb): 1.1Mb downloaded
Running setup.py egg_info for package Pygments
Downloading/unpacking Twisted==10.0.0 (from -r /tmp/saltycrane-requirements.txt (line 9))
Downloading Twisted-10.0.0.tar.bz2 (2.6Mb): 2.6Mb downloaded
Running setup.py egg_info for package Twisted
Obtaining django-contact-form from hg+http://bitbucket.org/ubernostrum/django-contact-form/#egg=django-contact-form (from -r /tmp/saltycrane-requirements.txt (line 12))
Cloning hg http://bitbucket.org/ubernostrum/django-contact-form/ to ./myenv/src/django-contact-form
Running setup.py egg_info for package django-contact-form
Obtaining akismet from hg+http://bitbucket.org/jezdez/akismet/#egg=akismet (from -r /tmp/saltycrane-requirements.txt (line 13))
Cloning hg http://bitbucket.org/jezdez/akismet/ to ./myenv/src/akismet
Running setup.py egg_info for package akismet
Downloading/unpacking Fabric==1.3.1 (from -r /tmp/saltycrane-requirements.txt (line 15))
Downloading Fabric-1.3.1.tar.gz (167Kb): 167Kb downloaded
Running setup.py egg_info for package Fabric
warning: no previously-included files matching '*' found under directory 'docs/_build'
warning: no files found matching 'fabfile.py'
Downloading/unpacking zope.interface (from Twisted==10.0.0->-r /tmp/saltycrane-requirements.txt (line 9))
Downloading zope.interface-3.8.0.tar.gz (111Kb): 111Kb downloaded
Running setup.py egg_info for package zope.interface
Downloading/unpacking ssh>=1.7.8 (from Fabric==1.3.1->-r /tmp/saltycrane-requirements.txt (line 15))
Downloading ssh-1.7.13.tar.gz (790Kb): 790Kb downloaded
Running setup.py egg_info for package ssh
Requirement already satisfied (use --upgrade to upgrade): distribute in ./myenv/lib/python2.6/site-packages/distribute-0.6.24-py2.6.egg (from zope.interface->Twisted==10.0.0->-r /tmp/saltycrane-requirements.txt (line 9))
Downloading/unpacking pycrypto>=2.1,!=2.4 (from ssh>=1.7.8->Fabric==1.3.1->-r /tmp/saltycrane-requirements.txt (line 15))
Downloading pycrypto-2.5.tar.gz (426Kb): 426Kb downloaded
Running setup.py egg_info for package pycrypto
Installing collected packages: psycopg2, Django, Markdown, Pygments, Twisted, django-contact-form, akismet, Fabric, BeautifulSoup, django-tagging, zope.interface, ssh, pycrypto
Running setup.py install for psycopg2
building 'psycopg2._psycopg' extension
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -DHAVE_MXDATETIME=1 -DPSYCOPG_DEFAULT_PYDATETIME=1 -DPSYCOPG_VERSION="2.4.2 (dt dec mx pq3 ext)" -DPG_VERSION_HEX=0x08040B -DPSYCOPG_EXTENSIONS=1 -DPSYCOPG_NEW_BOOLEAN=1 -DHAVE_PQFREEMEM=1 -I/usr/include/python2.6/mx -I/usr/include/python2.6 -I. -I/usr/include/postgresql -I/usr/include/postgresql/8.4/server -c psycopg/psycopgmodule.c -o build/temp.linux-x86_64-2.6/psycopg/psycopgmodule.o -Wdeclaration-after-statement
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -DHAVE_MXDATETIME=1 -DPSYCOPG_DEFAULT_PYDATETIME=1 -DPSYCOPG_VERSION="2.4.2 (dt dec mx pq3 ext)" -DPG_VERSION_HEX=0x08040B -DPSYCOPG_EXTENSIONS=1 -DPSYCOPG_NEW_BOOLEAN=1 -DHAVE_PQFREEMEM=1 -I/usr/include/python2.6/mx -I/usr/include/python2.6 -I. -I/usr/include/postgresql -I/usr/include/postgresql/8.4/server -c psycopg/green.c -o build/temp.linux-x86_64-2.6/psycopg/green.o -Wdeclaration-after-statement
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -DHAVE_MXDATETIME=1 -DPSYCOPG_DEFAULT_PYDATETIME=1 -DPSYCOPG_VERSION="2.4.2 (dt dec mx pq3 ext)" -DPG_VERSION_HEX=0x08040B -DPSYCOPG_EXTENSIONS=1 -DPSYCOPG_NEW_BOOLEAN=1 -DHAVE_PQFREEMEM=1 -I/usr/include/python2.6/mx -I/usr/include/python2.6 -I. -I/usr/include/postgresql -I/usr/include/postgresql/8.4/server -c psycopg/pqpath.c -o build/temp.linux-x86_64-2.6/psycopg/pqpath.o -Wdeclaration-after-statement
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -DHAVE_MXDATETIME=1 -DPSYCOPG_DEFAULT_PYDATETIME=1 -DPSYCOPG_VERSION="2.4.2 (dt dec mx pq3 ext)" -DPG_VERSION_HEX=0x08040B -DPSYCOPG_EXTENSIONS=1 -DPSYCOPG_NEW_BOOLEAN=1 -DHAVE_PQFREEMEM=1 -I/usr/include/python2.6/mx -I/usr/include/python2.6 -I. -I/usr/include/postgresql -I/usr/include/postgresql/8.4/server -c psycopg/utils.c -o build/temp.linux-x86_64-2.6/psycopg/utils.o -Wdeclaration-after-statement
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -DHAVE_MXDATETIME=1 -DPSYCOPG_DEFAULT_PYDATETIME=1 -DPSYCOPG_VERSION="2.4.2 (dt dec mx pq3 ext)" -DPG_VERSION_HEX=0x08040B -DPSYCOPG_EXTENSIONS=1 -DPSYCOPG_NEW_BOOLEAN=1 -DHAVE_PQFREEMEM=1 -I/usr/include/python2.6/mx -I/usr/include/python2.6 -I. -I/usr/include/postgresql -I/usr/include/postgresql/8.4/server -c psycopg/bytes_format.c -o build/temp.linux-x86_64-2.6/psycopg/bytes_format.o -Wdeclaration-after-statement
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -DHAVE_MXDATETIME=1 -DPSYCOPG_DEFAULT_PYDATETIME=1 -DPSYCOPG_VERSION="2.4.2 (dt dec mx pq3 ext)" -DPG_VERSION_HEX=0x08040B -DPSYCOPG_EXTENSIONS=1 -DPSYCOPG_NEW_BOOLEAN=1 -DHAVE_PQFREEMEM=1 -I/usr/include/python2.6/mx -I/usr/include/python2.6 -I. -I/usr/include/postgresql -I/usr/include/postgresql/8.4/server -c psycopg/connection_int.c -o build/temp.linux-x86_64-2.6/psycopg/connection_int.o -Wdeclaration-after-statement
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -DHAVE_MXDATETIME=1 -DPSYCOPG_DEFAULT_PYDATETIME=1 -DPSYCOPG_VERSION="2.4.2 (dt dec mx pq3 ext)" -DPG_VERSION_HEX=0x08040B -DPSYCOPG_EXTENSIONS=1 -DPSYCOPG_NEW_BOOLEAN=1 -DHAVE_PQFREEMEM=1 -I/usr/include/python2.6/mx -I/usr/include/python2.6 -I. -I/usr/include/postgresql -I/usr/include/postgresql/8.4/server -c psycopg/connection_type.c -o build/temp.linux-x86_64-2.6/psycopg/connection_type.o -Wdeclaration-after-statement
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -DHAVE_MXDATETIME=1 -DPSYCOPG_DEFAULT_PYDATETIME=1 -DPSYCOPG_VERSION="2.4.2 (dt dec mx pq3 ext)" -DPG_VERSION_HEX=0x08040B -DPSYCOPG_EXTENSIONS=1 -DPSYCOPG_NEW_BOOLEAN=1 -DHAVE_PQFREEMEM=1 -I/usr/include/python2.6/mx -I/usr/include/python2.6 -I. -I/usr/include/postgresql -I/usr/include/postgresql/8.4/server -c psycopg/cursor_int.c -o build/temp.linux-x86_64-2.6/psycopg/cursor_int.o -Wdeclaration-after-statement
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -DHAVE_MXDATETIME=1 -DPSYCOPG_DEFAULT_PYDATETIME=1 -DPSYCOPG_VERSION="2.4.2 (dt dec mx pq3 ext)" -DPG_VERSION_HEX=0x08040B -DPSYCOPG_EXTENSIONS=1 -DPSYCOPG_NEW_BOOLEAN=1 -DHAVE_PQFREEMEM=1 -I/usr/include/python2.6/mx -I/usr/include/python2.6 -I. -I/usr/include/postgresql -I/usr/include/postgresql/8.4/server -c psycopg/cursor_type.c -o build/temp.linux-x86_64-2.6/psycopg/cursor_type.o -Wdeclaration-after-statement
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -DHAVE_MXDATETIME=1 -DPSYCOPG_DEFAULT_PYDATETIME=1 -DPSYCOPG_VERSION="2.4.2 (dt dec mx pq3 ext)" -DPG_VERSION_HEX=0x08040B -DPSYCOPG_EXTENSIONS=1 -DPSYCOPG_NEW_BOOLEAN=1 -DHAVE_PQFREEMEM=1 -I/usr/include/python2.6/mx -I/usr/include/python2.6 -I. -I/usr/include/postgresql -I/usr/include/postgresql/8.4/server -c psycopg/lobject_int.c -o build/temp.linux-x86_64-2.6/psycopg/lobject_int.o -Wdeclaration-after-statement
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -DHAVE_MXDATETIME=1 -DPSYCOPG_DEFAULT_PYDATETIME=1 -DPSYCOPG_VERSION="2.4.2 (dt dec mx pq3 ext)" -DPG_VERSION_HEX=0x08040B -DPSYCOPG_EXTENSIONS=1 -DPSYCOPG_NEW_BOOLEAN=1 -DHAVE_PQFREEMEM=1 -I/usr/include/python2.6/mx -I/usr/include/python2.6 -I. -I/usr/include/postgresql -I/usr/include/postgresql/8.4/server -c psycopg/lobject_type.c -o build/temp.linux-x86_64-2.6/psycopg/lobject_type.o -Wdeclaration-after-statement
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -DHAVE_MXDATETIME=1 -DPSYCOPG_DEFAULT_PYDATETIME=1 -DPSYCOPG_VERSION="2.4.2 (dt dec mx pq3 ext)" -DPG_VERSION_HEX=0x08040B -DPSYCOPG_EXTENSIONS=1 -DPSYCOPG_NEW_BOOLEAN=1 -DHAVE_PQFREEMEM=1 -I/usr/include/python2.6/mx -I/usr/include/python2.6 -I. -I/usr/include/postgresql -I/usr/include/postgresql/8.4/server -c psycopg/notify_type.c -o build/temp.linux-x86_64-2.6/psycopg/notify_type.o -Wdeclaration-after-statement
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -DHAVE_MXDATETIME=1 -DPSYCOPG_DEFAULT_PYDATETIME=1 -DPSYCOPG_VERSION="2.4.2 (dt dec mx pq3 ext)" -DPG_VERSION_HEX=0x08040B -DPSYCOPG_EXTENSIONS=1 -DPSYCOPG_NEW_BOOLEAN=1 -DHAVE_PQFREEMEM=1 -I/usr/include/python2.6/mx -I/usr/include/python2.6 -I. -I/usr/include/postgresql -I/usr/include/postgresql/8.4/server -c psycopg/xid_type.c -o build/temp.linux-x86_64-2.6/psycopg/xid_type.o -Wdeclaration-after-statement
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -DHAVE_MXDATETIME=1 -DPSYCOPG_DEFAULT_PYDATETIME=1 -DPSYCOPG_VERSION="2.4.2 (dt dec mx pq3 ext)" -DPG_VERSION_HEX=0x08040B -DPSYCOPG_EXTENSIONS=1 -DPSYCOPG_NEW_BOOLEAN=1 -DHAVE_PQFREEMEM=1 -I/usr/include/python2.6/mx -I/usr/include/python2.6 -I. -I/usr/include/postgresql -I/usr/include/postgresql/8.4/server -c psycopg/adapter_asis.c -o build/temp.linux-x86_64-2.6/psycopg/adapter_asis.o -Wdeclaration-after-statement
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -DHAVE_MXDATETIME=1 -DPSYCOPG_DEFAULT_PYDATETIME=1 -DPSYCOPG_VERSION="2.4.2 (dt dec mx pq3 ext)" -DPG_VERSION_HEX=0x08040B -DPSYCOPG_EXTENSIONS=1 -DPSYCOPG_NEW_BOOLEAN=1 -DHAVE_PQFREEMEM=1 -I/usr/include/python2.6/mx -I/usr/include/python2.6 -I. -I/usr/include/postgresql -I/usr/include/postgresql/8.4/server -c psycopg/adapter_binary.c -o build/temp.linux-x86_64-2.6/psycopg/adapter_binary.o -Wdeclaration-after-statement
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -DHAVE_MXDATETIME=1 -DPSYCOPG_DEFAULT_PYDATETIME=1 -DPSYCOPG_VERSION="2.4.2 (dt dec mx pq3 ext)" -DPG_VERSION_HEX=0x08040B -DPSYCOPG_EXTENSIONS=1 -DPSYCOPG_NEW_BOOLEAN=1 -DHAVE_PQFREEMEM=1 -I/usr/include/python2.6/mx -I/usr/include/python2.6 -I. -I/usr/include/postgresql -I/usr/include/postgresql/8.4/server -c psycopg/adapter_datetime.c -o build/temp.linux-x86_64-2.6/psycopg/adapter_datetime.o -Wdeclaration-after-statement
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -DHAVE_MXDATETIME=1 -DPSYCOPG_DEFAULT_PYDATETIME=1 -DPSYCOPG_VERSION="2.4.2 (dt dec mx pq3 ext)" -DPG_VERSION_HEX=0x08040B -DPSYCOPG_EXTENSIONS=1 -DPSYCOPG_NEW_BOOLEAN=1 -DHAVE_PQFREEMEM=1 -I/usr/include/python2.6/mx -I/usr/include/python2.6 -I. -I/usr/include/postgresql -I/usr/include/postgresql/8.4/server -c psycopg/adapter_list.c -o build/temp.linux-x86_64-2.6/psycopg/adapter_list.o -Wdeclaration-after-statement
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -DHAVE_MXDATETIME=1 -DPSYCOPG_DEFAULT_PYDATETIME=1 -DPSYCOPG_VERSION="2.4.2 (dt dec mx pq3 ext)" -DPG_VERSION_HEX=0x08040B -DPSYCOPG_EXTENSIONS=1 -DPSYCOPG_NEW_BOOLEAN=1 -DHAVE_PQFREEMEM=1 -I/usr/include/python2.6/mx -I/usr/include/python2.6 -I. -I/usr/include/postgresql -I/usr/include/postgresql/8.4/server -c psycopg/adapter_pboolean.c -o build/temp.linux-x86_64-2.6/psycopg/adapter_pboolean.o -Wdeclaration-after-statement
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -DHAVE_MXDATETIME=1 -DPSYCOPG_DEFAULT_PYDATETIME=1 -DPSYCOPG_VERSION="2.4.2 (dt dec mx pq3 ext)" -DPG_VERSION_HEX=0x08040B -DPSYCOPG_EXTENSIONS=1 -DPSYCOPG_NEW_BOOLEAN=1 -DHAVE_PQFREEMEM=1 -I/usr/include/python2.6/mx -I/usr/include/python2.6 -I. -I/usr/include/postgresql -I/usr/include/postgresql/8.4/server -c psycopg/adapter_pdecimal.c -o build/temp.linux-x86_64-2.6/psycopg/adapter_pdecimal.o -Wdeclaration-after-statement
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -DHAVE_MXDATETIME=1 -DPSYCOPG_DEFAULT_PYDATETIME=1 -DPSYCOPG_VERSION="2.4.2 (dt dec mx pq3 ext)" -DPG_VERSION_HEX=0x08040B -DPSYCOPG_EXTENSIONS=1 -DPSYCOPG_NEW_BOOLEAN=1 -DHAVE_PQFREEMEM=1 -I/usr/include/python2.6/mx -I/usr/include/python2.6 -I. -I/usr/include/postgresql -I/usr/include/postgresql/8.4/server -c psycopg/adapter_pint.c -o build/temp.linux-x86_64-2.6/psycopg/adapter_pint.o -Wdeclaration-after-statement
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -DHAVE_MXDATETIME=1 -DPSYCOPG_DEFAULT_PYDATETIME=1 -DPSYCOPG_VERSION="2.4.2 (dt dec mx pq3 ext)" -DPG_VERSION_HEX=0x08040B -DPSYCOPG_EXTENSIONS=1 -DPSYCOPG_NEW_BOOLEAN=1 -DHAVE_PQFREEMEM=1 -I/usr/include/python2.6/mx -I/usr/include/python2.6 -I. -I/usr/include/postgresql -I/usr/include/postgresql/8.4/server -c psycopg/adapter_pfloat.c -o build/temp.linux-x86_64-2.6/psycopg/adapter_pfloat.o -Wdeclaration-after-statement
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -DHAVE_MXDATETIME=1 -DPSYCOPG_DEFAULT_PYDATETIME=1 -DPSYCOPG_VERSION="2.4.2 (dt dec mx pq3 ext)" -DPG_VERSION_HEX=0x08040B -DPSYCOPG_EXTENSIONS=1 -DPSYCOPG_NEW_BOOLEAN=1 -DHAVE_PQFREEMEM=1 -I/usr/include/python2.6/mx -I/usr/include/python2.6 -I. -I/usr/include/postgresql -I/usr/include/postgresql/8.4/server -c psycopg/adapter_qstring.c -o build/temp.linux-x86_64-2.6/psycopg/adapter_qstring.o -Wdeclaration-after-statement
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -DHAVE_MXDATETIME=1 -DPSYCOPG_DEFAULT_PYDATETIME=1 -DPSYCOPG_VERSION="2.4.2 (dt dec mx pq3 ext)" -DPG_VERSION_HEX=0x08040B -DPSYCOPG_EXTENSIONS=1 -DPSYCOPG_NEW_BOOLEAN=1 -DHAVE_PQFREEMEM=1 -I/usr/include/python2.6/mx -I/usr/include/python2.6 -I. -I/usr/include/postgresql -I/usr/include/postgresql/8.4/server -c psycopg/microprotocols.c -o build/temp.linux-x86_64-2.6/psycopg/microprotocols.o -Wdeclaration-after-statement
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -DHAVE_MXDATETIME=1 -DPSYCOPG_DEFAULT_PYDATETIME=1 -DPSYCOPG_VERSION="2.4.2 (dt dec mx pq3 ext)" -DPG_VERSION_HEX=0x08040B -DPSYCOPG_EXTENSIONS=1 -DPSYCOPG_NEW_BOOLEAN=1 -DHAVE_PQFREEMEM=1 -I/usr/include/python2.6/mx -I/usr/include/python2.6 -I. -I/usr/include/postgresql -I/usr/include/postgresql/8.4/server -c psycopg/microprotocols_proto.c -o build/temp.linux-x86_64-2.6/psycopg/microprotocols_proto.o -Wdeclaration-after-statement
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -DHAVE_MXDATETIME=1 -DPSYCOPG_DEFAULT_PYDATETIME=1 -DPSYCOPG_VERSION="2.4.2 (dt dec mx pq3 ext)" -DPG_VERSION_HEX=0x08040B -DPSYCOPG_EXTENSIONS=1 -DPSYCOPG_NEW_BOOLEAN=1 -DHAVE_PQFREEMEM=1 -I/usr/include/python2.6/mx -I/usr/include/python2.6 -I. -I/usr/include/postgresql -I/usr/include/postgresql/8.4/server -c psycopg/typecast.c -o build/temp.linux-x86_64-2.6/psycopg/typecast.o -Wdeclaration-after-statement
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -DHAVE_MXDATETIME=1 -DPSYCOPG_DEFAULT_PYDATETIME=1 -DPSYCOPG_VERSION="2.4.2 (dt dec mx pq3 ext)" -DPG_VERSION_HEX=0x08040B -DPSYCOPG_EXTENSIONS=1 -DPSYCOPG_NEW_BOOLEAN=1 -DHAVE_PQFREEMEM=1 -I/usr/include/python2.6/mx -I/usr/include/python2.6 -I. -I/usr/include/postgresql -I/usr/include/postgresql/8.4/server -c psycopg/adapter_mxdatetime.c -o build/temp.linux-x86_64-2.6/psycopg/adapter_mxdatetime.o -Wdeclaration-after-statement
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions build/temp.linux-x86_64-2.6/psycopg/psycopgmodule.o build/temp.linux-x86_64-2.6/psycopg/green.o build/temp.linux-x86_64-2.6/psycopg/pqpath.o build/temp.linux-x86_64-2.6/psycopg/utils.o build/temp.linux-x86_64-2.6/psycopg/bytes_format.o build/temp.linux-x86_64-2.6/psycopg/connection_int.o build/temp.linux-x86_64-2.6/psycopg/connection_type.o build/temp.linux-x86_64-2.6/psycopg/cursor_int.o build/temp.linux-x86_64-2.6/psycopg/cursor_type.o build/temp.linux-x86_64-2.6/psycopg/lobject_int.o build/temp.linux-x86_64-2.6/psycopg/lobject_type.o build/temp.linux-x86_64-2.6/psycopg/notify_type.o build/temp.linux-x86_64-2.6/psycopg/xid_type.o build/temp.linux-x86_64-2.6/psycopg/adapter_asis.o build/temp.linux-x86_64-2.6/psycopg/adapter_binary.o build/temp.linux-x86_64-2.6/psycopg/adapter_datetime.o build/temp.linux-x86_64-2.6/psycopg/adapter_list.o build/temp.linux-x86_64-2.6/psycopg/adapter_pboolean.o build/temp.linux-x86_64-2.6/psycopg/adapter_pdecimal.o build/temp.linux-x86_64-2.6/psycopg/adapter_pint.o build/temp.linux-x86_64-2.6/psycopg/adapter_pfloat.o build/temp.linux-x86_64-2.6/psycopg/adapter_qstring.o build/temp.linux-x86_64-2.6/psycopg/microprotocols.o build/temp.linux-x86_64-2.6/psycopg/microprotocols_proto.o build/temp.linux-x86_64-2.6/psycopg/typecast.o build/temp.linux-x86_64-2.6/psycopg/adapter_mxdatetime.o -lpq -o build/lib.linux-x86_64-2.6/psycopg2/_psycopg.so
no previously-included directories found matching 'doc/src/_build'
Running setup.py install for Django
changing mode of build/scripts-2.6/django-admin.py from 644 to 755
changing mode of /tmp/myenv/bin/django-admin.py to 755
Running setup.py install for Markdown
changing mode of build/scripts-2.6/markdown.py from 644 to 755
changing mode of /tmp/myenv/bin/markdown.py to 755
Running setup.py install for Pygments
Installing pygmentize script to /tmp/myenv/bin
Running setup.py install for Twisted
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -I/usr/include/python2.6 -c conftest.c -o conftest.o
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -I/usr/include/python2.6 -c conftest.c -o conftest.o
building 'twisted.runner.portmap' extension
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -I/usr/include/python2.6 -c twisted/runner/portmap.c -o build/temp.linux-x86_64-2.6/twisted/runner/portmap.o
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions build/temp.linux-x86_64-2.6/twisted/runner/portmap.o -o build/lib.linux-x86_64-2.6/twisted/runner/portmap.so
building 'twisted.protocols._c_urlarg' extension
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -I/usr/include/python2.6 -c twisted/protocols/_c_urlarg.c -o build/temp.linux-x86_64-2.6/twisted/protocols/_c_urlarg.o
twisted/protocols/_c_urlarg.c: In function ‘unquote’:
twisted/protocols/_c_urlarg.c:41: warning: ‘tmp’ may be used uninitialized in this function
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions build/temp.linux-x86_64-2.6/twisted/protocols/_c_urlarg.o -o build/lib.linux-x86_64-2.6/twisted/protocols/_c_urlarg.so
building 'twisted.test.raiser' extension
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -I/usr/include/python2.6 -c twisted/test/raiser.c -o build/temp.linux-x86_64-2.6/twisted/test/raiser.o
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions build/temp.linux-x86_64-2.6/twisted/test/raiser.o -o build/lib.linux-x86_64-2.6/twisted/test/raiser.so
building 'twisted.python._epoll' extension
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -I/usr/include/python2.6 -c twisted/python/_epoll.c -o build/temp.linux-x86_64-2.6/twisted/python/_epoll.o
twisted/python/_epoll.c: In function ‘__pyx_f_6_epoll_5epoll___dealloc__’:
twisted/python/_epoll.c:168: warning: label ‘__pyx_L1’ defined but not used
twisted/python/_epoll.c: In function ‘__pyx_f_6_epoll_5epoll_wait’:
twisted/python/_epoll.c:432: warning: label ‘__pyx_L7’ defined but not used
twisted/python/_epoll.c:430: warning: label ‘__pyx_L6’ defined but not used
twisted/python/_epoll.c: In function ‘__pyx_tp_new_6_epoll_epoll’:
twisted/python/_epoll.c:508: warning: unused variable ‘p’
twisted/python/_epoll.c: In function ‘__pyx_tp_dealloc_6_epoll_epoll’:
twisted/python/_epoll.c:513: warning: unused variable ‘p’
twisted/python/_epoll.c: In function ‘__pyx_tp_traverse_6_epoll_epoll’:
twisted/python/_epoll.c:528: warning: unused variable ‘p’
twisted/python/_epoll.c:527: warning: unused variable ‘e’
twisted/python/_epoll.c: In function ‘__pyx_tp_clear_6_epoll_epoll’:
twisted/python/_epoll.c:533: warning: unused variable ‘p’
twisted/python/_epoll.c: At top level:
twisted/python/_epoll.c:32: warning: ‘__Pyx_UnpackItem’ declared ‘static’ but never defined
twisted/python/_epoll.c:33: warning: ‘__Pyx_EndUnpack’ declared ‘static’ but never defined
twisted/python/_epoll.c:34: warning: ‘__Pyx_PrintItem’ declared ‘static’ but never defined
twisted/python/_epoll.c:35: warning: ‘__Pyx_PrintNewline’ declared ‘static’ but never defined
twisted/python/_epoll.c:37: warning: ‘__Pyx_ReRaise’ declared ‘static’ but never defined
twisted/python/_epoll.c:38: warning: ‘__Pyx_Import’ declared ‘static’ but never defined
twisted/python/_epoll.c:39: warning: ‘__Pyx_GetExcValue’ declared ‘static’ but never defined
twisted/python/_epoll.c:40: warning: ‘__Pyx_ArgTypeTest’ declared ‘static’ but never defined
twisted/python/_epoll.c:41: warning: ‘__Pyx_TypeTest’ declared ‘static’ but never defined
twisted/python/_epoll.c:42: warning: ‘__Pyx_GetStarArgs’ declared ‘static’ but never defined
twisted/python/_epoll.c:43: warning: ‘__Pyx_WriteUnraisable’ declared ‘static’ but never defined
twisted/python/_epoll.c:45: warning: ‘__Pyx_ImportType’ declared ‘static’ but never defined
twisted/python/_epoll.c:46: warning: ‘__Pyx_SetVtable’ declared ‘static’ but never defined
twisted/python/_epoll.c:47: warning: ‘__Pyx_GetVtable’ declared ‘static’ but never defined
twisted/python/_epoll.c:48: warning: ‘__Pyx_CreateClass’ declared ‘static’ but never defined
twisted/python/_epoll.c:50: warning: ‘__Pyx_InitStrings’ declared ‘static’ but never defined
twisted/python/_epoll.c:51: warning: ‘__Pyx_InitCApi’ declared ‘static’ but never defined
twisted/python/_epoll.c:52: warning: ‘__Pyx_ImportModuleCApi’ declared ‘static’ but never defined
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions build/temp.linux-x86_64-2.6/twisted/python/_epoll.o -o build/lib.linux-x86_64-2.6/twisted/python/_epoll.so
building 'twisted.python._initgroups' extension
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -I/usr/include/python2.6 -c twisted/python/_initgroups.c -o build/temp.linux-x86_64-2.6/twisted/python/_initgroups.o
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions build/temp.linux-x86_64-2.6/twisted/python/_initgroups.o -o build/lib.linux-x86_64-2.6/twisted/python/_initgroups.so
changing mode of build/scripts-2.6/mktap from 644 to 755
changing mode of build/scripts-2.6/tap2deb from 644 to 755
changing mode of build/scripts-2.6/tap2rpm from 644 to 755
changing mode of build/scripts-2.6/trial from 644 to 755
changing mode of build/scripts-2.6/pyhtmlizer from 644 to 755
changing mode of build/scripts-2.6/tapconvert from 644 to 755
changing mode of build/scripts-2.6/manhole from 644 to 755
changing mode of build/scripts-2.6/twistd from 644 to 755
changing mode of build/scripts-2.6/ckeygen from 644 to 755
changing mode of build/scripts-2.6/cftp from 644 to 755
changing mode of build/scripts-2.6/conch from 644 to 755
changing mode of build/scripts-2.6/tkconch from 644 to 755
changing mode of build/scripts-2.6/mailmail from 644 to 755
changing mode of build/scripts-2.6/lore from 644 to 755
changing mode of /tmp/myenv/bin/mktap to 755
changing mode of /tmp/myenv/bin/ckeygen to 755
changing mode of /tmp/myenv/bin/cftp to 755
changing mode of /tmp/myenv/bin/conch to 755
changing mode of /tmp/myenv/bin/tap2deb to 755
changing mode of /tmp/myenv/bin/tap2rpm to 755
changing mode of /tmp/myenv/bin/lore to 755
changing mode of /tmp/myenv/bin/trial to 755
changing mode of /tmp/myenv/bin/pyhtmlizer to 755
changing mode of /tmp/myenv/bin/tkconch to 755
changing mode of /tmp/myenv/bin/tapconvert to 755
changing mode of /tmp/myenv/bin/manhole to 755
changing mode of /tmp/myenv/bin/twistd to 755
changing mode of /tmp/myenv/bin/mailmail to 755
Running setup.py develop for django-contact-form
Creating /tmp/myenv/lib/python2.6/site-packages/django-contact-form.egg-link (link to .)
Adding django-contact-form 0.3 to easy-install.pth file
Installed /tmp/myenv/src/django-contact-form
Running setup.py develop for akismet
Creating /tmp/myenv/lib/python2.6/site-packages/akismet.egg-link (link to .)
Adding akismet 0.1.5 to easy-install.pth file
Installed /tmp/myenv/src/akismet
Running setup.py install for Fabric
warning: no previously-included files matching '*' found under directory 'docs/_build'
warning: no files found matching 'fabfile.py'
Installing fab script to /tmp/myenv/bin
Running setup.py install for BeautifulSoup
Running setup.py install for django-tagging
Running setup.py install for zope.interface
building 'zope.interface._zope_interface_coptimizations' extension
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -I/usr/include/python2.6 -c src/zope/interface/_zope_interface_coptimizations.c -o build/temp.linux-x86_64-2.6/src/zope/interface/_zope_interface_coptimizations.o
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions build/temp.linux-x86_64-2.6/src/zope/interface/_zope_interface_coptimizations.o -o build/lib.linux-x86_64-2.6/zope/interface/_zope_interface_coptimizations.so
Skipping installation of /tmp/myenv/lib/python2.6/site-packages/zope/__init__.py (namespace package)
Installing /tmp/myenv/lib/python2.6/site-packages/zope.interface-3.8.0-py2.6-nspkg.pth
Running setup.py install for ssh
Running setup.py install for pycrypto
checking for gcc... gcc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables...
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed
checking for __gmpz_init in -lgmp... no
checking for __gmpz_init in -lmpir... no
checking whether mpz_powm is declared... no
checking whether mpz_powm_sec is declared... no
checking how to run the C preprocessor... gcc -E
checking for grep that handles long lines and -e... /bin/grep
checking for egrep... /bin/grep -E
checking for ANSI C header files... yes
checking for sys/types.h... yes
checking for sys/stat.h... yes
checking for stdlib.h... yes
checking for string.h... yes
checking for memory.h... yes
checking for strings.h... yes
checking for inttypes.h... yes
checking for stdint.h... yes
checking for unistd.h... yes
checking for inttypes.h... (cached) yes
checking limits.h usability... yes
checking limits.h presence... yes
checking for limits.h... yes
checking stddef.h usability... yes
checking stddef.h presence... yes
checking for stddef.h... yes
checking for stdint.h... (cached) yes
checking for stdlib.h... (cached) yes
checking for string.h... (cached) yes
checking wchar.h usability... yes
checking wchar.h presence... yes
checking for wchar.h... yes
checking for inline... inline
checking for int16_t... yes
checking for int32_t... yes
checking for int64_t... yes
checking for int8_t... yes
checking for size_t... yes
checking for uint16_t... yes
checking for uint32_t... yes
checking for uint64_t... yes
checking for uint8_t... yes
checking for stdlib.h... (cached) yes
checking for GNU libc compatible malloc... yes
checking for memmove... yes
checking for memset... yes
configure: creating ./config.status
config.status: creating src/config.h
warning: GMP or MPIR library not found; Not building Crypto.PublicKey._fastmath.
building 'Crypto.Hash._MD2' extension
gcc -pthread -fno-strict-aliasing -fwrapv -Wall -Wstrict-prototypes -fPIC -std=c99 -O3 -fomit-frame-pointer -Isrc/ -I/usr/include/python2.6 -c src/MD2.c -o build/temp.linux-x86_64-2.6/src/MD2.o
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions build/temp.linux-x86_64-2.6/src/MD2.o -o build/lib.linux-x86_64-2.6/Crypto/Hash/_MD2.so
building 'Crypto.Hash._MD4' extension
gcc -pthread -fno-strict-aliasing -fwrapv -Wall -Wstrict-prototypes -fPIC -std=c99 -O3 -fomit-frame-pointer -Isrc/ -I/usr/include/python2.6 -c src/MD4.c -o build/temp.linux-x86_64-2.6/src/MD4.o
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions build/temp.linux-x86_64-2.6/src/MD4.o -o build/lib.linux-x86_64-2.6/Crypto/Hash/_MD4.so
building 'Crypto.Hash._SHA256' extension
gcc -pthread -fno-strict-aliasing -fwrapv -Wall -Wstrict-prototypes -fPIC -std=c99 -O3 -fomit-frame-pointer -Isrc/ -I/usr/include/python2.6 -c src/SHA256.c -o build/temp.linux-x86_64-2.6/src/SHA256.o
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions build/temp.linux-x86_64-2.6/src/SHA256.o -o build/lib.linux-x86_64-2.6/Crypto/Hash/_SHA256.so
building 'Crypto.Hash._SHA224' extension
gcc -pthread -fno-strict-aliasing -fwrapv -Wall -Wstrict-prototypes -fPIC -std=c99 -O3 -fomit-frame-pointer -Isrc/ -I/usr/include/python2.6 -c src/SHA224.c -o build/temp.linux-x86_64-2.6/src/SHA224.o
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions build/temp.linux-x86_64-2.6/src/SHA224.o -o build/lib.linux-x86_64-2.6/Crypto/Hash/_SHA224.so
building 'Crypto.Hash._SHA384' extension
gcc -pthread -fno-strict-aliasing -fwrapv -Wall -Wstrict-prototypes -fPIC -std=c99 -O3 -fomit-frame-pointer -Isrc/ -I/usr/include/python2.6 -c src/SHA384.c -o build/temp.linux-x86_64-2.6/src/SHA384.o
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions build/temp.linux-x86_64-2.6/src/SHA384.o -o build/lib.linux-x86_64-2.6/Crypto/Hash/_SHA384.so
building 'Crypto.Hash._SHA512' extension
gcc -pthread -fno-strict-aliasing -fwrapv -Wall -Wstrict-prototypes -fPIC -std=c99 -O3 -fomit-frame-pointer -Isrc/ -I/usr/include/python2.6 -c src/SHA512.c -o build/temp.linux-x86_64-2.6/src/SHA512.o
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions build/temp.linux-x86_64-2.6/src/SHA512.o -o build/lib.linux-x86_64-2.6/Crypto/Hash/_SHA512.so
building 'Crypto.Hash._RIPEMD160' extension
gcc -pthread -fno-strict-aliasing -fwrapv -Wall -Wstrict-prototypes -fPIC -std=c99 -O3 -fomit-frame-pointer -DPCT_LITTLE_ENDIAN=1 -Isrc/ -I/usr/include/python2.6 -c src/RIPEMD160.c -o build/temp.linux-x86_64-2.6/src/RIPEMD160.o
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions build/temp.linux-x86_64-2.6/src/RIPEMD160.o -o build/lib.linux-x86_64-2.6/Crypto/Hash/_RIPEMD160.so
building 'Crypto.Cipher.AES' extension
gcc -pthread -fno-strict-aliasing -fwrapv -Wall -Wstrict-prototypes -fPIC -std=c99 -O3 -fomit-frame-pointer -Isrc/ -I/usr/include/python2.6 -c src/AES.c -o build/temp.linux-x86_64-2.6/src/AES.o
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions build/temp.linux-x86_64-2.6/src/AES.o -o build/lib.linux-x86_64-2.6/Crypto/Cipher/AES.so
building 'Crypto.Cipher.ARC2' extension
gcc -pthread -fno-strict-aliasing -fwrapv -Wall -Wstrict-prototypes -fPIC -std=c99 -O3 -fomit-frame-pointer -Isrc/ -I/usr/include/python2.6 -c src/ARC2.c -o build/temp.linux-x86_64-2.6/src/ARC2.o
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions build/temp.linux-x86_64-2.6/src/ARC2.o -o build/lib.linux-x86_64-2.6/Crypto/Cipher/ARC2.so
building 'Crypto.Cipher.Blowfish' extension
gcc -pthread -fno-strict-aliasing -fwrapv -Wall -Wstrict-prototypes -fPIC -std=c99 -O3 -fomit-frame-pointer -Isrc/ -I/usr/include/python2.6 -c src/Blowfish.c -o build/temp.linux-x86_64-2.6/src/Blowfish.o
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions build/temp.linux-x86_64-2.6/src/Blowfish.o -o build/lib.linux-x86_64-2.6/Crypto/Cipher/Blowfish.so
building 'Crypto.Cipher.CAST' extension
gcc -pthread -fno-strict-aliasing -fwrapv -Wall -Wstrict-prototypes -fPIC -std=c99 -O3 -fomit-frame-pointer -Isrc/ -I/usr/include/python2.6 -c src/CAST.c -o build/temp.linux-x86_64-2.6/src/CAST.o
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions build/temp.linux-x86_64-2.6/src/CAST.o -o build/lib.linux-x86_64-2.6/Crypto/Cipher/CAST.so
building 'Crypto.Cipher.DES' extension
gcc -pthread -fno-strict-aliasing -fwrapv -Wall -Wstrict-prototypes -fPIC -std=c99 -O3 -fomit-frame-pointer -Isrc/ -Isrc/libtom/ -I/usr/include/python2.6 -c src/DES.c -o build/temp.linux-x86_64-2.6/src/DES.o
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions build/temp.linux-x86_64-2.6/src/DES.o -o build/lib.linux-x86_64-2.6/Crypto/Cipher/DES.so
building 'Crypto.Cipher.DES3' extension
gcc -pthread -fno-strict-aliasing -fwrapv -Wall -Wstrict-prototypes -fPIC -std=c99 -O3 -fomit-frame-pointer -Isrc/ -Isrc/libtom/ -I/usr/include/python2.6 -c src/DES3.c -o build/temp.linux-x86_64-2.6/src/DES3.o
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions build/temp.linux-x86_64-2.6/src/DES3.o -o build/lib.linux-x86_64-2.6/Crypto/Cipher/DES3.so
building 'Crypto.Cipher.ARC4' extension
gcc -pthread -fno-strict-aliasing -fwrapv -Wall -Wstrict-prototypes -fPIC -std=c99 -O3 -fomit-frame-pointer -Isrc/ -I/usr/include/python2.6 -c src/ARC4.c -o build/temp.linux-x86_64-2.6/src/ARC4.o
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions build/temp.linux-x86_64-2.6/src/ARC4.o -o build/lib.linux-x86_64-2.6/Crypto/Cipher/ARC4.so
building 'Crypto.Cipher.XOR' extension
gcc -pthread -fno-strict-aliasing -fwrapv -Wall -Wstrict-prototypes -fPIC -std=c99 -O3 -fomit-frame-pointer -Isrc/ -I/usr/include/python2.6 -c src/XOR.c -o build/temp.linux-x86_64-2.6/src/XOR.o
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions build/temp.linux-x86_64-2.6/src/XOR.o -o build/lib.linux-x86_64-2.6/Crypto/Cipher/XOR.so
building 'Crypto.Util.strxor' extension
gcc -pthread -fno-strict-aliasing -fwrapv -Wall -Wstrict-prototypes -fPIC -std=c99 -O3 -fomit-frame-pointer -Isrc/ -I/usr/include/python2.6 -c src/strxor.c -o build/temp.linux-x86_64-2.6/src/strxor.o
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions build/temp.linux-x86_64-2.6/src/strxor.o -o build/lib.linux-x86_64-2.6/Crypto/Util/strxor.so
building 'Crypto.Util._counter' extension
gcc -pthread -fno-strict-aliasing -fwrapv -Wall -Wstrict-prototypes -fPIC -std=c99 -O3 -fomit-frame-pointer -Isrc/ -I/usr/include/python2.6 -c src/_counter.c -o build/temp.linux-x86_64-2.6/src/_counter.o
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions build/temp.linux-x86_64-2.6/src/_counter.o -o build/lib.linux-x86_64-2.6/Crypto/Util/_counter.so
Successfully installed psycopg2 Django Markdown Pygments Twisted django-contact-form akismet Fabric BeautifulSoup django-tagging zope.interface ssh pycrypto
Cleaning up...</pre>
<h4 id="virtualenv-mod_python">Use virtualenv with Django and mod_python</h4>
<p>Here is how to use a virtualenv with
<a href="http://www.modpython.org/">mod_python</a>.
This is taken from
<em><a href="http://mydjangoblog.com/2009/03/30/django-mod_python-and-virtualenv/">
Django, mod_python and virtualenv</a></em>.
For more information, see the virtualenv documentation on
<a href="http://virtualenv.openplans.org/#using-virtualenv-without-bin-python">using
virtualenv without bin/python</a>.
</p>
<ul>
<li>Create a file <code>/srv/SaltyCrane/myvirtualdjango.py</code>:
<pre class="python">activate_this = "/srv/python-environments/saltycrane/bin/activate_this.py"
execfile(activate_this, dict(__file__=activate_this))
from django.core.handlers.modpython import handler</pre>
</li>
<li>Edit your <code>httpd.conf</code>
<pre> <Location "/">
SetHandler python-program
PythonHandler myvirtualdjango
SetEnv DJANGO_SETTINGS_MODULE iwiwdsmi.settings
PythonPath "['/srv/SaltyCrane',] + sys.path"
PythonDebug Off
</Location></pre>
</li>
</ul>
<h4 id="virtualenv-mod_wsgi">Use virtualenv with Django and mod_wsgi</h4>
<p><em>Added 2009-09-27:</em>
Here is how I set up my virtualenv with
<a href="http://code.google.com/p/modwsgi/">mod_wsgi</a>.
To use the the packages in my virtualenv,
I used <code>site.addsitedir</code> at the
top of my .wsgi application file. You may also want to set the WSGIPythonHome
variable in your httpd.conf file (outside of any VirtualHost sections). For detailed information
on using mod_wsgi with virtualenv, see the
<a href="http://code.google.com/p/modwsgi/wiki/VirtualEnvironments">Virtual
Environments section</a> of the modwsgi project documentation.
</p>
<ul>
<li><code>/srv/SaltyCrane/saltycrane.wsgi</code>:
<pre>import os
import sys
<b>import site
site.addsitedir('/srv/python-environments/saltycrane/lib/python2.5/site-packages')</b>
os.environ['DJANGO_SETTINGS_MODULE'] = 'iwiwdsmi.settings'
sys.path.append('/srv/SaltyCrane')
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()</pre>
</li>
<li><code>httpd.conf</code>:
<pre> WSGIScriptAlias / /srv/SaltyCrane/saltycrane.wsgi</pre>
</li>
</ul>
<h4 id="background">Background / Discussion</h4>
<p> Below are some links to
some essential information on pip, virtualenv, and Python packaging
(mostly from the creator of pip and virtualenv, Ian Bicking) and some further
discussion on the state of Python packaging. <em>(Updated 2012-11-17)</em>
</p>
<ul>
<li><a href="http://blog.ianbicking.org/2007/10/10/workingenv-is-dead-long-live-virtualenv/">
Workingenv is dead, long live Virtualenv!</a> by Ian Bicking (2007-10-10)
</li>
<li><a href="http://groups.google.com/group/django-developers/msg/5407cdb400157259">
mailing list post explaining a number of package management technologies</a>
by Kevin Teague (2008-09-14)
</li>
<li><a href="http://www.coactivate.org/projects/topp-engineering/blog/2008/09/24/pyinstall-a-new-hope/">
pyinstall: A New Hope</a> by Ian Bicking (2008-09-24)
</li>
<li><a href="http://blog.ianbicking.org/2008/10/01/pyinstall-pybundles/">
pyinstall pybundles</a> by Ian Bicking (2008-10-01)
</li>
<li><a href="http://blog.ianbicking.org/2008/10/28/pyinstall-is-dead-long-live-pip/">
pyinstall is dead, long live pip!</a> by Ian Bicking (2008-10-28)
</li>
<li><a href="http://www.b-list.org/weblog/2008/dec/14/packaging/">
On packaging</a> by James Bennett (2008-12-14)
<li><a href="http://blog.ianbicking.org/2008/12/14/a-few-corrections-to-on-packaging/">
A Few Corrections To "On Packaging"</a> by Ian Bicking (2008-12-14)
</li>
<li><a href="http://www.b-list.org/weblog/2008/dec/15/pip/">
Why I like pip</a> by James Bennett (2008-12-15)
</li>
<li><a href="http://blog.ianbicking.org/2008/12/16/using-pip-requirements/">
Using pip Requirements</a> by Ian Bicking (2008-12-16)
</li>
<li><a href="http://guide.python-distribute.org/">
The Hitchhiker’s Guide to Packaging</a> (Documentation for distribute) (<em>Added 2010-04-13:</em>)
</li>
<li>
<a href="http://blog.ziade.org/2012/09/10/dear-django-help-python-packaging/">Dear Django, help Python Packaging</a>
by Tarek Ziadé (2012-09-10)
</li>
<li>
Brett Cannon's summary of <a href="https://plus.google.com/u/0/115362263245161504841/posts/UcBQK8P4jw3">various PEPs regarding packaging</a> (2012-09-11)</a>
</li>
<li>
<a href="http://blog.ziade.org/2012/09/12/dear-pyramid-help-python-packaging/">Dear Pyramid, help Python Packaging</a>
by Tarek Ziadé (2012-09-12)
</li>
<li>
<a href="https://twitter.com/tarek_ziade/status/269749074872373248/photo/1">The Chronology of Python Packaging Part 1</a>,
<a href="https://twitter.com/tarek_ziade/status/269754203868643328/photo/1">Part 2</a> by Tarek Ziadé (2012-11-17)
</li>
</ul>
Notes on using nginx with mod_python and Django
2009-04-28T00:26:11-07:00https://www.saltycrane.com/blog/2009/04/notes-using-nginx-mod_python-and-django/<p>Here are my notes on setting up
<a href="http://nginx.net/">nginx</a> as a reverse proxy with
Apache, mod_python, and Django on Ubuntu Intrepid. Nginx is used
to serve my static media files while all other requests are passed
on to the Apache/mod_python/Django web server.
</p>
<p>I realize
<a href="http://code.google.com/p/modwsgi/">mod_wsgi</a> has become the
<a href="http://simonwillison.net/2009/Apr/1/modwsgi/">preferred</a>
<a href="http://collingrady.wordpress.com/2009/01/06/mod_python-versus-mod_wsgi/">
way</a> to deploy Django, but I'm a little behind the times and am still
using mod_python. I hope to switch to mod_wsgi soon.
</p>
<p>I <a href="http://www.saltycrane.com/blog/2008/12/card-store-project-4-notes-using-amazons-cloudfront/">
have been using</a> Amazon's
<a href="http://aws.amazon.com/cloudfront/">CloudFront</a> service for
delivering my static media files. As far as I can tell, it has worked well.
My main reason for switching to nginx is so I can skip the extra step of
uploading to Amazon. Regarding my concern about the memory footprint of nginx,
it looks like it is using around 5mb with my two process configuration.
</p>
<p>My configuration parameters are shown below. I'm running two sites, SaltyCrane and
HandsOnCards on a <a href="http://www.slicehost.com/">Slicehost</a> 256mb plan.
</p>
<table width=550>
<tr>
<td><b>Description</b></td>
<td><b>HandsOnCards</b></td>
<td><b>SaltyCrane</b></td>
</tr>
<tr>
<td>Redirection</td>
<td><code class="code2">http://www.handsoncards.com</code> is redirected to <code class="code2">http://handsoncards.com</code></td>
<td><code class="code2">http://saltycrane.com</code> is redirected to <code class="code2">http://www.saltycrane.com</code></td>
</tr>
<tr>
<td>Static media filesystem path</td>
<td><code class="code2">/srv/HandsOnCards/handsoncards/static/</code></td>
<td><code class="code2">/srv/SaltyCrane/iwiwdsmi/media/</code></td>
</tr>
<tr>
<td>Static media web path</td>
<td><code class="code2">/site_media/</code></td>
<td><code class="code2">/site_media/</code></td>
</tr>
<tr>
<td>Django settings.py file location</td>
<td><code class="code2">/srv/HandsOnCards/handsoncards/settings.py</code></td>
<td><code class="code2">/srv/SaltyCrane/iwiwdsmi/settings.py</code></td>
</tr>
<tr>
<td>Additional Python packages path</td>
<td><code class="code2">/srv/python-packages</code></td>
<td><code class="code2">/srv/python-packages</code></td>
</tr>
</table>
<h4>Install nginx</h4>
<p>Some recommend installing nginx from source, but I took the easier route
and used Ubuntu's package manager.</p>
<pre>sudo apt-get install nginx</pre>
<h4>Nginx configuration</h4>
<p>Edit <code>/etc/nginx/nginx.conf</code>:</p>
<pre class="nginx">user www-data www-data;
worker_processes 2;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
sendfile on;
tcp_nopush on;
keepalive_timeout 3;
tcp_nodelay off;
gzip on;
gzip_comp_level 2;
gzip_proxied any;
gzip_types text/plain text/html text/css application/x-javascript text/xml
application/xml application/xml+rss text/javascript;
server {
listen 80;
server_name www.handsoncards.com;
rewrite ^/(.*) http://handsoncards.com/$1 permanent;
}
server {
listen 80;
server_name handsoncards.com;
access_log /var/log/nginx/handsoncards.com.access.log;
error_log /var/log/nginx/handsoncards.com.error.log;
location / {
proxy_pass http://127.0.0.1:8080/;
include /etc/nginx/proxy.conf;
}
location /site_media/ {
alias /srv/HandsOnCards/handsoncards/static/;
expires 24h;
}
}
server {
listen 80;
server_name saltycrane.com;
rewrite ^/(.*) http://www.saltycrane.com/$1 permanent;
}
server {
listen 80;
server_name www.saltycrane.com;
access_log /var/log/nginx/saltycrane.com.access.log;
error_log /var/log/nginx/saltycrane.com.error.log;
location / {
proxy_pass http://127.0.0.1:8080/;
include /etc/nginx/proxy.conf;
}
location /site_media/ {
alias /srv/SaltyCrane/iwidsmi/media/;
expires 24h;
}
}
}
</pre>
<p>Edit <code>/etc/nginx/proxy.conf</code>:</p>
<pre class="nginx">proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffers 32 4k;</pre>
<p>Restart Nginx</p>
<pre>sudo /etc/init.d/nginx restart</pre>
<h4>Install Apache</h4>
<p>I already had Apache and mod_python installed, but in case you don't:</p>
<pre>apt-get install apache2 apache2-mpm-prefork
apt-get install libapache2-mod-python</pre>
<h4>Apache Configuration</h4>
<p>Edit <code>/etc/apache2/httpd.conf</code>:</p>
<pre>MaxClients 2
MaxRequestsPerChild 350
KeepAlive Off
NameVirtualHost 127.0.0.1:8080
Listen 8080
<VirtualHost 127.0.0.1:8080>
ServerName www.saltycrane.com
<Location "/">
SetHandler python-program
PythonHandler django.core.handlers.modpython
SetEnv DJANGO_SETTINGS_MODULE iwiwdsmi.settings
PythonPath "['/srv/SaltyCrane', '/srv/python-packages'] + sys.path"
PythonDebug Off
</Location>
</VirtualHost>
<VirtualHost 127.0.0.1:8080>
ServerName handsoncards.com
<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>
</VirtualHost></pre>
<p>Edit <code>/etc/apache2/ports.conf</code> and comment out the following two lines:</p>
<pre>#NameVirtualHost *:80
#Listen 80</pre>
<p>Restart Apache</p>
<pre>sudo /etc/init.d/apache2 restart</pre>
<h4>Add Django's reverse proxy middleware</h4>
<p>Edit your <code>settings.py</code> file to include
<code class="code2">django.middleware.http.SetRemoteAddrFromForwardedFor</code> in
<code>MIDDLEWARE_CLASSES</code>. This allows your Django application to use the real
IP address of the client instead of <code>127.0.0.1</code> from your nginx proxy. It
sets Django's <code>request.META['REMOTE_ADDR']</code>
to be <code>request.META['HTTP_X_FORWARDED_FOR']</code> which we set above in nginx's
<code>proxy.conf</code>. For more information, see the
<a href="http://docs.djangoproject.com/en/dev/ref/middleware/#reverse-proxy-middleware">
Middleware reference</a> in the Django documentation.
</p>
<pre>MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.middleware.http.SetRemoteAddrFromForwardedFor',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
)</pre>
<h4>References</h4>
<ul>
<li><a href="http://bart.whahay.net/blog/2009/04/06/setting-up-nginx-django.html">
http://bart.whahay.net/blog/2009/04/06/setting-up-nginx-django.html</a> (my main reference)</li>
<li><a href="http://articles.slicehost.com/2009/3/5/ubuntu-intrepid-nginx-configuration">
http://articles.slicehost.com/2009/3/5/ubuntu-intrepid-nginx-configuration</a> (general nginx configuration)</li>
<li><a href="http://articles.slicehost.com/2009/3/6/ubuntu-intrepid-nginx-virtual-hosts-1">
http://articles.slicehost.com/2009/3/6/ubuntu-intrepid-nginx-virtual-hosts-1</a> (nginx virtual hosts configuration)</li>
<li><a href="http://forum.slicehost.com/comments.php?DiscussionID=2964">
http://forum.slicehost.com/comments.php?DiscussionID=2964</a> (explains the
difference between <code>root</code> and <code>alias</code> in nginx.conf.)</li>
</ul>
<h4>Errors</h4>
<ul>
<li>A 502 Bad Gateway from Nginx probably means there is something wrong with your Apache setup</li>
<li>An Internal Server Error from Apache means something is probably wrong with your Django
setup.</li>
</ul>
Card store project #5: Redirecting my www-prefixed domain to my non-www-prefixed domain
2008-12-28T16:53:07-08:00https://www.saltycrane.com/blog/2008/12/card-store-project-5-redirecting-my-www-prefixed-domain-my-non-www-prefixed-domain/<p>For search engine optimization and analytics, I wanted to make
<a href="http://www.handsoncards.com/">http://www.handsoncards.com/</a>
permanently redirect to <a href="http://handsoncards.com/">http://handsoncards.com/</a>.
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 <code>.htaccess</code> 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 <code>httpd.conf</code> file, but I got an infinite redirect loop.
Finally, I found the solution from <a href="http://www.webmasterworld.com/apache/3217088.htm">
this discussion</a>. Here's what I did. I'm using Django, mod_python, and
Apache on Ubuntu at Slicehost.
</p>
<ul>
<li>Enable mod_rewrite. Logged in as root:
<pre>a2enmod rewrite</pre>
</li>
<li>Edit <code>/etc/apache2/httpd.conf</code>:
<pre><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></pre>
</li>
<li>Restart Apache:
<pre>/etc/init.d/apache2 restart</pre>
</li>
</ul>
Card store project #4: Notes on using Amazon's CloudFront
2008-12-27T17:22:45-08:00https://www.saltycrane.com/blog/2008/12/card-store-project-4-notes-using-amazons-cloudfront/<p>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
<a href="http://aws.amazon.com/s3/">S3</a> or
<a href="http://aws.amazon.com/cloudfront/">CloudFront</a> with Django on
VPS hosting. Though there is <a href="http://holovaty.com/blog/archive/2006/04/07/0927">
Adrian's post</a> from 2006, I see more articles about serving media
files with <a href="http://www.lighttpd.net/">lighttpd</a> or, more recently,
<a href="http://wiki.codemongers.com/Main">nginx</a>. 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
<a href="http://www.slicehost.com/">Slicehost</a>. 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?</p>
<p>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
<a href="http://handsoncards.com/">here</a>.
I'm still not sure if I should be happy with the site's speed though. I did a quick
<a href="http://www.danga.com/memcached/">memcached</a> 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 <a href="http://www.satchmoproject.com/">Satchmo</a> store.</p>
<h4>Sign up for S3</h4>
<ul>
<li>Sign up for <a href="http://aws.amazon.com/">Amazon
Web Services</a></li>
<li>Sign up for <a href="http://aws.amazon.com/s3/">Simple Storage
Service</a></li>
<li>Take note of your "Access Key ID" and your "Secret Access Key" under
"Your Account", "Access Identifiers"</li>
</ul>
<h4>Get S3 Python library</h4>
<ul>
<li>Download the <a href="http://developer.amazonwebservices.com/connect/entry.jspa?externalID=134">
Amazon S3 Python library</a></li>
<li>Unpack it, and put <code>s3-example-libraries/python/S3.py</code>
<a href="http://www.saltycrane.com/blog/2008/08/somewhere-your-python-path/">somewhere on your
Python path</a>.</li>
</ul>
<h4>Create a S3 bucket</h4>
<ul>
<li>Create a file named <code>create_bucket.py</code>:
<pre>import S3
ACCESS_KEY = 'myaccesskey'
SECRET_KEY = 'mysecretaccesskey'
BUCKET_NAME = 'handsoncards'
conn = S3.AWSAuthConnection(ACCESS_KEY, SECRET_KEY)
conn.create_bucket(BUCKET_NAME)</pre>
</li>
<li>Run it:
<pre>python create_bucket.py</pre>
</li>
</ul>
<h4>Upload files to S3</h4>
<ul>
<li>Download <a href="http://www.holovaty.com/code/update_s3.py">Adrian's S3 upload
script</a> and save it to <code>/srv/HandsOnCards/handsoncards/bin/update_s3.py</code></li>
<li>Edit the script with the correct values for <code>AWS_ACCESS_KEY_ID</code>,
<code>AWS_SECRET_ACCESS_KEY</code>, and <code>BUCKET_NAME</code>.</li>
<li>Upload files. (Assumes static directory is linked to <code>/var/www/site_media</code>).
<pre>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</pre>
</li>
</ul>
<h4>Set up CloudFront</h4>
<ul>
<li>Sign up for CloudFront</li>
<li>Get the S3 Fox firefox plugin</li>
<li>Click "Manage Accounts" and enter access key and secret key</li>
<li>Right click on your bucket (handsoncards) and select "Manage Distributions"
Enter a "Comment" and optional CNAME, then click "Create Distribution".
</li>
<li>Wait a while while the distribution is created. Take note of the
"Domain Name". For me it is: <code>http://d16z1yuk7jeryy.cloudfront.net</code>
</li>
<li>Click the refresh button until the "Status" says "Deployed"</li>
</ul>
<h4>Update settings and templates to use CloudFront</h4>
<ul>
<li>In settings.py set MEDIA_URL and ADMIN_MEDIA_PREFIX as follows:
<pre>MEDIA_URL = 'http://d16z1yuk7jeryy.cloudfront.net/site_media/'
ADMIN_MEDIA_PREFIX = 'http://d16z1yuk7jeryy.cloudfront.net/admin_media/'</pre>
</li>
<li>In your base.html template and all other templates, replace
<code>/site_media/</code> with
<code>http://d16z1yuk7jeryy.cloudfront.net/site_media/</code>.
</li>
</ul>
<h4>Update 2009-06-08: Add "Expires" headers</h4>
<p>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 <a href="#c2711">orip</a> for this tip.</p>
<pre><span style="color:red">from datetime import datetime, timedelta</span>
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()
<span style="color:red">expires = datetime.utcnow() + timedelta(days=365)
expires = expires.strftime("%a, %d %b %Y %H:%M:%S GMT")</span>
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,
<span style="color:red">'Expires': expires,</span>
})
if __name__ == "__main__":
update_s3()</pre>
<br>
For more information:<br>
<ul>
<li><a href="http://developer.yahoo.com/performance/rules.html#expires">
"Add an Expires or a Cache-Control Header" section of the Yahoo Developer Network
"Best Practices for Speeding Up Your Web Site" guide</a></li>
<li><a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21">
Section 14.21 of the HTTP specification</a></li>
<li><a href="http://www.drunkenfist.com/304/2007/12/26/setting-far-future-expires-headers-for-images-in-amazon-s3/">
Rob Larsen's blog post: "Setting Far Future Expires Headers For Images In Amazon S3"</a></li>
</ul>
<h4>Update 2009-10-21: Add CNAME record</h4>
<p>Go to your DNS Zone manager and add a CNAME record with the following parameters:</p>
<ul>
<li>Type: CNAME</li>
<li>Name: static</li>
<li>Data: d16z1yuk7jeryy.cloudfront.net. <em>(Don't forget the period at the end!)</li>
<li>TTL: <em>whatever you want. I left it at 86400</em></li>
</ul>
<p>Now wherever I previously would have used <code>http://d16z1yuk7jeryy.cloudfront.net</code>, I can replace it with <code>http://static.handsoncards.com</code>.</p>
Card store project #3: Installing Satchmo, part 2
2008-12-12T23:20:12-08:00https://www.saltycrane.com/blog/2008/12/card-store-project-3-installing-satchmo-part-2/<p>Here are my initial steps in setting up my <a href="http://www.satchmoproject.com/">
Satchmo</a> store. It is meant to be a continuation of my previous post,
<a href="http://www.saltycrane.com/blog/2008/12/card-store-project-2-installing-satchmo-django-postgresql-and-apache-ubuntu-slicehost/">
Installing Satchmo, Django, PostgreSQL, and Apache on Ubuntu at Slicehost</a>.
I am using Satchmo version 0.8, released 2008-11-25. I am combining the instructions
from the <a href="http://www.satchmoproject.com/docs/rel/0.8/new_installation.html">
Satchmo documentation</a> and
<a href="http://gosatchmo.com/starting-a-new-store-real-world-project-layout">Bruce's
blog post</a>. 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.</p>
<p>Here is my final project directory structure mostly copied from Bruce's post:</p>
<pre>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</pre>
<h4>Set up project</h4>
<ul>
<li>Create directory structure:
<pre>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
</pre>
</li>
<li>Create log file
<pre>touch satchmo.log
chmod 666 satchmo.log</pre>
</li>
<li>Create the cache directory:
<pre>mkdir django_cache
chmod 777 django_cache</pre>
</li>
<li>Copy settings files:
<pre>cp /srv/satchmo-0.8/satchmo/local_settings-customize.py local_settings.py
cp /srv/satchmo-0.8/satchmo/settings-customize.py settings.py</pre>
</li>
<li>Set the Python path:
<pre>export PYTHONPATH=/srv/python-packages:/srv/HandsOnCards</pre>
</li>
<li>Copy static files:
<pre>python manage.py satchmo_copy_static
chmod 777 static/css
chmod 777 static/images
chmod 777 static/js</pre>
</li>
<li>Set the root URLconf. Edit <code>settings.py</code>:
<pre>ROOT_URLCONF = 'handsoncards.urls'</pre>
</li>
<li>Create <code>handsoncards/store/urls.py</code> to only contain the following:
<pre>from django.conf.urls.defaults import *
from satchmo.urls import urlpatterns</pre>
</li>
<li>Edit <code>handsoncards/urls.py</code> to only contain the following:
<pre>from django.conf.urls.defaults import *
from handsoncards.store.urls import urlpatterns</pre>
</li>
<li>Configure templates. Edit <code>local_settings.py</code>:
<pre>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"),
)</pre>
(I also commented out TEMPLATE_DIRS in settings.py since this replaces it.)
</li>
<li>Install the store app. Edit <code>settings.py</code>:
<pre>INSTALLED_APPS = (
[...]
'handsoncards.store', #should usually be last
)</pre>
</li>
<li>From my previous post, use the following database settings in
<code>settings.py</code>:
<pre>DATABASE_ENGINE = 'postgresql_psycopg2'
DATABASE_NAME = 'django_db'
DATABASE_USER = 'django_user'
DATABASE_PASSWORD = 'django_password'
DATABASE_HOST = ''
DATABASE_PORT = ''</pre>
</li>
</ul>
<h4>Configure misc. settings</h4>
<p>In settings.py:</p>
<pre>
LOCAL_DEV = False
ADMINS = (
(Sofeng', sofeng@myemail.com'),
)
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',
[...]
}</pre>
<p>In local_settings.py:</p>
<pre>SITE_DOMAIN = "handsoncards.com"
SITE_NAME = "HandsOnCards.com"
CACHE_BACKEND = "file://" + DIRNAME + "/django_cache"</pre>
<h4>Test and install data</h4>
<ul>
<li>Set the Python path:
<pre>export PYTHONPATH=/srv/python-packages:/srv/HandsOnCards</pre>
</li>
<li>Test Satchmo setup:
<pre>python manage.py satchmo_check</pre>
Results:
<pre>Checking your satchmo configuration.
Using Django version 1.0.2 final
Using Satchmo version 0.8
Your configuration has no errors.</pre>
</li>
<li>Create database tables:
<pre>python manage.py syncdb</pre>
Go ahead and create a superuser
</li>
<li>Load country data:
<pre>python manage.py satchmo_load_l10n</pre>
</li>
<li>Load US tax table:
<pre>python manage.py satchmo_load_us_tax</pre>
</li>
</ul>
<h4>Set up httpd.conf and static media links</h4>
<ul>
<li>Create symbolic links in <code>/var/www</code>:
<pre>cd /var/www
ln -s /srv/python-packages/django/contrib/admin/media admin_media
ln -s /srv/HandsOnCards/handsoncards/static site_media</pre>
</li>
<li>Edit <code>/etc/apache2/httpd.conf</code>:
<pre><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></pre>
</li>
<li>Restart apache:
<pre>/etc/init.d/apache2 restart</pre>
</li>
</ul>
<h4>View the store</h4>
<p>Navigate to <code>http://<em>[your domain or slice ip address]</em>/shop/</code>
in your browser. You should see an empty test store.
</p>
<h4>Create a Store Configuration</h4>
<ul>
<li>Go to <code>http://<em>[your domain or slice ip address]</em>/admin/</code> and log in.</li>
<li>Update the "Site". Click on "Sites". Click on "example.com" and set your domain.</li>
<li>Under the "Shop" heading, click on "Store Configurations". Click
"Add Store Configuration" and fill in the information.</li>
</ul>
<h4>Create categories and products</h4>
<ul>
<li>Create a Category:
<ul>
<li>Next to "Categories", click "Add"</li>
<li>Fill in the data</li>
</ul>
</li>
<li>Create a Product:
<ul>
<li>Next to "Products", click "Add"</li>
<li>Fill in the data</li>
</ul>
</li>
</ul>
<h4>Customize templates and CSS</h4>
<ul>
<li>Copy the desired templates from <code>/srv/satchmo-0.8/satchmo/templates</code>
to <code>/srv/HandsOnCards/handsoncards/templates/store</code> and edit them.
</li>
<li>To customize the style, edit <code>handsoncards/static/css/style.css</code>
</li>
</ul>
<h4>Redirect '/' to '/shop/'</h4>
<ul>
<li>Edit <code>handsoncards/urls.py</code> as follows:
<pre>from django.conf.urls.defaults import *
from handsoncards.store.urls import urlpatterns
urlpatterns += patterns(
'',
(r'^$', 'django.views.generic.simple.redirect_to', {'url': '/shop/'}),
)</pre>
</li>
</ul>
django-admin error: Unknown command: 'startproject'
2008-12-12T12:29:58-08:00https://www.saltycrane.com/blog/2008/12/django-admin-error-unknown-command-startproject/If the DJANGO_SETTINGS_MODULE environment variable has been set, then the "startproject" command will be disabled in django-admin.py. See <a href="http://code.djangoproject.com/ticket/8639">this ticket</a>.
Card store project #2: Installing Satchmo, Django, PostgreSQL, and Apache on Ubuntu at Slicehost
2008-12-11T21:21:22-08:00https://www.saltycrane.com/blog/2008/12/card-store-project-2-installing-satchmo-django-postgresql-and-apache-ubuntu-slicehost/<p>As I mentioned in my
<a href="http://www.saltycrane.com/blog/2008/12/card-store-project-1-plans/">previous
post</a>, I'm planning to set up an e-commerce site using the
<a href="http://www.satchmoproject.com/">Satchmo</a> shopping cart framework.
Satchmo is built on the
<a href="http://www.satchmoproject.com/">Django</a> web framework which is
written in the
<a href="http://www.python.org/">Python</a> programming language. Satchmo
has a lot of <a href="http://www.satchmoproject.com/docs/svn/features.html">features</a>
built in which means it saves you a lot of work implementing them yourself.
Check out <a href="http://www.youtube.com/watch?v=d42a4g650Ws">this video
introduction to Satchmo</a> at this year's DjangoCon for more information.</p>
<p>After reading <a href="http://groups.google.com/group/satchmo-users/browse_thread/thread/b453d6453b6a28cf?pli=1">
this discussion</a> on the Satchmo mailing list, I decided to use
<a href="http://www.slicehost.com/">Slicehost</a> 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
<a href="http://www.postgresql.org/">PostgreSQL</a>,
<a href="http://httpd.apache.org/">Apache</a>,
and <a href="http://www.modpython.org/">mod_python</a> on
<a href="http://www.ubuntu.com/">Ubuntu</a> Intrepid at Slicehost.
It seems <a href="http://www.lighttpd.net/">lighttpd</a> and
<a href="http://nginx.net/">nginx</a> are popular lightweight alternatives to
Apache on VPS setups. I do not know too much about this, but may explore these
in the future.
</p>
<p>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
<a href="http://www.satchmoproject.com/docs/rel/0.8.1/new_installation.html">
Satchmo Installation documentation</a>. <em>Update 2008-12-12: I did write
another blog post-- see <a href="http://www.saltycrane.com/blog/2008/12/card-store-project-3-installing-satchmo-part-2/">
Installing Satchmo, part 2</a>.</em>
</p>
<h4>Setup Slicehost</h4>
<ul>
<li>Sign up for an account at <a href="http://www.slicehost.com/">http://www.slicehost.com/</a>
I chose Ubuntu Intrepid for my operating system and named my slice "toad".
</li>
<li>I followed the following two excellent Slicehost tutorials:
<a href="http://articles.slicehost.com/2008/4/25/ubuntu-hardy-setup-page-1">Ubuntu Setup Part 1</a>
and <a href="http://articles.slicehost.com/2008/4/25/ubuntu-hardy-setup-page-2">Part 2</a>.
</li>
<li>An important step is setting the system locale. Since I'm in the United States,
I used the following commands (run as root):
<pre>locale-gen en_US.UTF-8
update-locale LANG=en_US.UTF-8</pre>
</li>
</ul>
<h4>Install apache, mod_python, postgresql, and postgres python bindings</h4>
<ul>
<li>Run as root (or use sudo):
<pre>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</pre>
</li>
</ul>
<h4>Configure apache</h4>
<ul>
<li>I used the following Slicehost tutorials:
<a href="http://articles.slicehost.com/2008/4/25/ubuntu-hardy-installing-apache-and-php5">Install Apache</a>,
<a href="http://articles.slicehost.com/2008/4/28/ubuntu-hardy-apache-configuration-1">Configure Apache Part 1</a>,
<a href="http://articles.slicehost.com/2008/4/28/ubuntu-hardy-apache-configuration-2">Configure Apache Part 2</a>
</li>
<li>Of particular note, when installing apache, I got this warning:
<pre>apache2: Could not reliably determine the server's fully qualified domain name, using 127.0.0.1 for ServerName</pre>
Fix it by setting the ServerName in <code>/etc/apache2/apache2.conf</code>:
<pre>ServerName yourdomain.com
ServerTokens Prod</pre>
See the <a href="http://httpd.apache.org/docs/2.2/mod/core.html#servername">Apache
docs</a> for more information on ServerName.
</li>
</ul>
<h4>Install Django 1.0.2</h4>
<ul>
<li>Run the following as root:
<pre>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</pre>
</li>
</ul>
<h4>Test Django (optional)</h4>
<ul>
<li>Run the following as root:
<pre>export PYTHONPATH=/srv/python-packages
/srv/Django-1.0.2-final/django/bin/django-admin.py startproject testdjango</pre>
</li>
<li>Edit <code>/etc/apache2/httpd.conf</code>:
<pre><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></pre>
</li>
<li>Restart apache:
<pre>/etc/init.d/apache2 restart</pre>
Navigate to your slice's IP address in your browser and you
should see the Django "It worked!" page.
</li>
</ul>
<h4>Install Satchmo</h4>
<p>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
<a href="http://peak.telecommunity.com/DevCenter/EasyInstall">easy_install</a>,
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.
</p>
<p>Note, I use the directory <code>/srv/python-packages</code> 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 <code>httpd.conf</code> file. See my post,
<a href="http://www.saltycrane.com/blog/2008/08/somewhere-your-python-path/">
Somewhere on your Python path</a> for more information.
</p>
<ul>
<li>Install Satchmo prerequisites. Run the following commands as root:
<br><b>Install APT packages:</b>
<pre>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</pre>
<br><b>Install django-registration using easy_install:</b>
<pre>apt-get install python-setuptools python-dev build-essential
easy_install django-registration</pre>
<br><b>Install comment_utils source:</b>
<pre>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</pre>
</li>
<li>Install Satchmo 0.8. Run the following as root: <em>Update 2008-12-12:
I changed this to use release 0.8 instead of the SVN trunk version.</em>
<pre>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</pre>
To install the SVN trunk version instead,
<pre>cd /srv
svn co svn://satchmoproject.com/satchmo/trunk
mv trunk satchmo_revXXXX
cd /srv/python-packages
ln -s ../satchmo_revXXXX/satchmo</pre>
</li>
</ul>
<h4 id="postgres">Setup PostgreSQL</h4>
<p>I followed the
<a href="http://www.punteney.com/writes/setting-django-slicehost-ubuntu-hardy-postgres-apa/">instructions
at Punteney.com</a> for setting up Django and Postgres on Slicehost.
</p>
<ul>
<li>If you didn't already install Postgres above, run the following as root:
<pre>apt-get install postgresql
apt-get install python-psycopg2</pre>
</li>
<li>Change the password for the "postgres" Unix user. (run as root or use sudo)
<pre>passwd -d postgres
su postgres -c passwd</pre>
</li>
<li>Change password for "postgres" user in the database.
(From Punteney's article, "It's convenient
to have the two passwords match, but not required.")
<pre>su postgres -c psql template1</pre>
<pre>ALTER USER postgres WITH PASSWORD 'postgres_user_password';
template1=\q</pre>
</li>
<li>Create django user:
<pre>su postgres
createuser -P django_user</pre>
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 <code>my_password</code>.)
</li>
<li>Create a database. Still <code>su</code>'ed as the "postgres" user:
<pre>psql template1</pre>
<pre>CREATE DATABASE django_db OWNER django_user ENCODING 'UTF8';
\q</pre>
<pre>exit</pre>
(exit from postgres su)
</li>
<li>Configure access to the database. Edit
<code>/etc/postgresql/8.3/main/pg_hba.conf</code>:
<pre>local all postgres ident sameuser
local django_db django_user md5</pre>
</li>
<li>Restart the postgres server:
<pre>/etc/init.d/postgresql-8.3 restart</pre>
</li>
<li>In your Django project, use the following database settings in
<code>settings.py</code>:
<pre>DATABASE_ENGINE = 'postgresql_psycopg2'
DATABASE_NAME = 'django_db'
DATABASE_USER = 'django_user'
DATABASE_PASSWORD = 'my_password'
DATABASE_HOST = ''
DATABASE_PORT = ''</pre>
</li>
<li>Run <code>syncdb</code>:
<pre>cd /srv/yourproject
python manage.py syncdb</pre>
</li>
</ul>
<h4>Setup a domain name</h4>
<ul>
<li>Register for a domain name. Google for "domain registration" for
options.
</li>
<li>Follow the
<a href="http://articles.slicehost.com/2007/10/24/creating-dns-records">
Slicehost directions</a> 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).
</li>
</ul>
Card store project #1: Plans
2008-12-08T11:09:35-08:00https://www.saltycrane.com/blog/2008/12/card-store-project-1-plans/<p>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.
</p>
<h4>General plans</h4>
<p>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 <a href="http://www.python.org/">Python</a>
scripts,
<a href ="http://twistedmatrix.com/trac/">Twisted</a>
Perspective Broker communication, and custom robotic cutting, folding, and
mail delivery. Finally, I plan to <a href="http://en.wikipedia.org/wiki/Pinky_and_the_Brain">
take over the world</a>.
</p>
<h4>Desktop publishing plans</h4>
<p>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 <a href="http://www.gimp.org/">Gimp</a>,
and deal with all the hardware/software
incompatibilities that come with it. Besides Gimp,
I plan to use <a href="http://www.scribus.net/">Scribus</a>, a pretty cool
open-source desktop publishing program. For printing, I've already purchased
a <a href="http://www.office.xerox.com/printers/color-printers/phaser-8560/enus.html">
Xerox Phaser 8560DN</a> 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.
</p>
<h4>Business-related plans</h4>
<p>I plan to run the business as a <a href="http://en.wikipedia.org/wiki/Sole_proprietorship">
sole proprietorship</a> since it is the simplest of the
<a href="http://www.sba.gov/smallbusinessplanner/start/chooseastructure/START_FORMS_OWNERSHIP.html">
business structure options</a>. 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
<a href="http://entrepreneurs.about.com/od/businessstructure/a/doingbusinessas.htm">
ficticious business name</a>. For more information, see the
<a href="http://www.sba.gov/smallbusinessplanner/index.html">
U.S. Small Business Administration guide</a>.
</p>
<h4>Website plans</h4>
<p>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.
</p>
<ul>
<li><a href="http://www.satchmoproject.com/">Satchmo</a>:
Satchmo is an open source e-commerce framework built
on Python and
<a href="http://www.djangoproject.com/">Django</a>.
It is <a href="http://www.satchmoproject.com/blog/">actively
developed</a>, has a number of
<a href="http://www.satchmoproject.com/docs/svn/features.html">features</a>,
and, since it is written in Python/Django, is easily hackable should I have
custom needs.</li>
<li><a href="http://www.slicehost.com/">Slicehost</a> hosting: I originally thought
I could use <a href="http://www.webfaction.com/">Webfaction</a> shared hosting since
it has served me well for this blog. However, after reading
<a href="http://groups.google.com/group/satchmo-users/browse_thread/thread/b453d6453b6a28cf">
this discussion on Satchmo hosting</a>, I realized I needed more RAM and
decided to go with VPS hosting at Slicehost.</li>
<li>I plan
to use <a href="http://aws.amazon.com/s3/">Amazon S3</a> for media storage.
I gained experience with this service at work. Also, after reading
Adrian Holovaty's (somewhat dated) <a href="http://holovaty.com/blog/archive/2006/04/07/0927">
post on using Django and S3 for media files</a>, I implemented S3 storage
for this blog. With the beta release of <a href="http://aws.amazon.com/cloudfront/">
Amazon CloudFront</a>, performance should be even better.
</li>
<li>Other technologies: I'll stick with <a href="http://www.ubuntu.com/">Ubuntu</a>
for the
server OS. I'll start with <a href="http://www.apache.org/">Apache</a> 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
<a href="http://www.mysql.com/">MySQL</a> from work, I plan to use
<a href="http://www.postgresql.org/">PostgreSQL</a> 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
<a href="http://www.danga.com/memcached/">memcached</a>,
<a href="http://nginx.net/">nginx</a>, and
<a href="http://www.squid-cache.org/">squid-cache</a>.
</li>
</ul>
<br>
<p>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.
</p>
Python UnicodeEncodeError: 'ascii' codec can't encode character
2008-11-06T00:07:47-08:00https://www.saltycrane.com/blog/2008/11/python-unicodeencodeerror-ascii-codec-cant-encode-character/<pre>
UnicodeEncodeError: 'ascii' codec can't encode character u'\xa1'
in position 0: ordinal not in range(128)
</pre>
<p>If you've ever gotten this error, Django's <code>smart_str</code>
function might be able to help. I found this from James Bennett's
article, <a href="http://www.b-list.org/weblog/2007/nov/10/unicode/">
Unicode in the real world</a>. He provides a very good explanation
of Python's Unicode and bytestrings, their use in Django, and using
Django's Unicode utilities for working with non-Unicode-friendly
Python libraries. Here are my notes from his article as it applies
to the above error. Much of the wording is directly from James
Bennett's article.
</p>
<p>This error occurs when you pass
a Unicode string containing non-English characters (Unicode characters
beyond 128) to something that expects an ASCII bytestring.
The default encoding for a Python bytestring is ASCII, "which handles exactly
128 (English) characters". This is why trying to convert Unicode
characters beyond 128 produces the error.</p>
<p>The good news is that you can encode Python bytestrings in other encodings
besides ASCII. Django's <code>smart_str</code> function in the
<code>django.utils.encoding</code> module, converts a Unicode string
to a bytestring using a default encoding of UTF-8.</p>
<p>Here is an example using the built-in function, <code>str</code>:</p>
<pre class="python">a = u'\xa1'
print str(a) # this throws an exception</pre>
<p>Results:</p>
<pre>Traceback (most recent call last):
File "unicode_ex.py", line 3, in <module>
print str(a) # this throws an exception
UnicodeEncodeError: 'ascii' codec can't encode character u'\xa1' in position 0: ordinal not in range(128)</pre>
<p>Here is an example using <code>smart_str</code>:</p>
<pre class="python">from django.utils.encoding import smart_str, smart_unicode
a = u'\xa1'
print smart_str(a)</pre>
<p>Results:</p>
<pre>¡</pre>
<h4 id="definitions">Definitions</h4>
<ul>
<li>Unicode string: sequence of Unicode characters</li>
<li>Python bytestring: a series of bytes which represent a sequence of characters.
It's default encoding is ASCII. This is the "normal", non-Unicode string in
Python <3.0.</li>
<li>encoding: a code that pairs a sequence of characters with a series of bytes</li>
<li>ASCII: an encoding which handles 128 English characters</li>
<li>UTF-8: a popular encoding used for Unicode strings which is backwards compatible
with ASCII for the first 128 characters. It uses one to four bytes for each
character.</li>
</ul>
<h4 id="str-unicode-operations">Operations related to str and unicode objects</h4>
<ul>
<li><a href="http://docs.python.org/library/stdtypes.html#str.encode">
<code>unicode.encode()</code></a> - converts to str</li>
<li><a href="http://docs.python.org/library/stdtypes.html#str.decode">
<code>str.decode()</code></a> - converts to unicode</li>
<li><a href="http://docs.python.org/library/functions.html#unicode">
<code>unicode(str, encoding)</code></a> - converts to unicode</li>
<li><a href="http://docs.python.org/library/functions.html#ord">
<code>ord(c)</code></a> - returns the Unicode code point of the character</li>
<li><a href="http://docs.python.org/library/functions.html#chr">
<code>chr(i)</code></a> - returns a str object for the given ASCII code (inverse of ord() for 8-bit strings)</li>
<li><a href="http://docs.python.org/library/functions.html#unichr">
<code>unichr(i)</code></a> - returns a unicode object for the given Unicode code (inverse of ord() for Unicode strings)</li>
</li>
</ul>
<h4 id="str-operations-table">Table of operations on str types (Python 2.7)</h4>
<pre>
| x -> | 'i' | '¡' | '\xa1' | chr(105) | u'\xa1'.encode('utf-8') |
|---------------------------+------------------+------------------+--------------+------------------+-------------------------+
| type(x) | <type 'str'> | <type 'str'> | <type 'str'> | <type 'str'> | <type 'str'> |
| ord(x) | 105 | NA | 161 | 105 | NA |
| type(str(x)) | <type 'str'> | <type 'str'> | <type 'str'> | <type 'str'> | <type 'str'> |
| type(unicode(x)) | <type 'unicode'> | DecodeError | DecodeError | <type 'unicode'> | DecodeError |
| type(unicode(x, 'utf-8')) | <type 'unicode'> | <type 'unicode'> | DecodeError | <type 'unicode'> | <type 'unicode'> |
| type(unicode(x, 'ascii')) | <type 'unicode'> | DecodeError | DecodeError | <type 'unicode'> | DecodeError |
| type(x.decode('utf-8')) | <type 'unicode'> | <type 'unicode'> | DecodeError | <type 'unicode'> | <type 'unicode'> |
| type(x.encode('utf-8')) | <type 'str'> | DecodeError | DecodeError | <type 'str'> | DecodeError |
| type(x.decode('ascii')) | <type 'unicode'> | DecodeError | DecodeError | <type 'unicode'> | DecodeError |
| type(x.encode('ascii')) | <type 'str'> | DecodeError | DecodeError | <type 'str'> | DecodeError |
</pre>
<h4 id="unicode-operations-table">Table of operations on unicode types (Python 2.7)</h4>
<pre>
| x -> | u'i' | u'¡' | u'\xa1' | unichr(161) | '¡'.decode('utf-8') |
|---------------------------+------------------+------------------+------------------+------------------+---------------------|
| type(x) | <type 'unicode'> | <type 'unicode'> | <type 'unicode'> | <type 'unicode'> | <type 'unicode'> |
| ord(x) | 105 | 161 | 161 | 161 | 161 |
| type(str(x)) | <type 'str'> | EncodeError | EncodeError | EncodeError | EncodeError |
| type(unicode(x)) | <type 'unicode'> | <type 'unicode'> | <type 'unicode'> | <type 'unicode'> | <type 'unicode'> |
| type(unicode(x, 'utf-8')) | <not supported> | <not supported> | <not supported> | <not supported> | <not supported> |
| type(unicode(x, 'ascii')) | <not supported> | <not supported> | <not supported> | <not supported> | <not supported> |
| type(x.decode('utf-8')) | <type 'unicode'> | EncodeError | EncodeError | EncodeError | EncodeError |
| type(x.encode('utf-8')) | <type 'str'> | <type 'str'> | <type 'str'> | <type 'str'> | <type 'str'> |
| type(x.decode('ascii')) | <type 'unicode'> | EncodeError | EncodeError | EncodeError | EncodeError |
| type(x.encode('ascii')) | <type 'str'> | EncodeError | EncodeError | EncodeError | EncodeError |
</pre>
<h4 id="unicode-unit-tests">Unicode unit tests (Python 2.7)</h4>
<pre class="python">import io
import os.path
import shutil
import tempfile
import unittest
class UnicodeTestCase(unittest.TestCase):
codepoint105_as_unicode = unichr(105)
codepoint105_as_bytestring = 'i'
codepoint105_as_bytestring_ascii = unichr(105).encode('ascii')
codepoint105_as_bytestring_utf8 = unichr(105).encode('utf-8')
codepoint105_as_bytestring_latin1 = unichr(105).encode('latin-1')
codepoint105_as_bytestring_cp950 = unichr(105).encode('cp950')
codepoint161_as_unicode = unichr(161)
codepoint161_as_bytestring_utf8 = unichr(161).encode('utf-8')
codepoint161_as_bytestring_latin1 = unichr(161).encode('latin-1')
def setUp(self):
self.tempdir = tempfile.mkdtemp(prefix='tmp-ditest-')
self.codepoint105_ascii_filepath = os.path.join(self.tempdir, 'codepoint105_ascii')
self.codepoint105_utf8_filepath = os.path.join(self.tempdir, 'codepoint105_utf8')
self.codepoint105_latin1_filepath = os.path.join(self.tempdir, 'codepoint105_latin1')
self.codepoint161_ascii_filepath = 'codepoint 161 cannot be encoded using ascii'
self.codepoint161_utf8_filepath = os.path.join(self.tempdir, 'codepoint161_utf8')
self.codepoint161_latin1_filepath = os.path.join(self.tempdir, 'codepoint161_latin1')
with io.open(self.codepoint105_ascii_filepath, 'w', encoding='ascii') as f:
f.write(self.codepoint105_as_unicode)
with io.open(self.codepoint105_utf8_filepath, 'w', encoding='utf8') as f:
f.write(self.codepoint105_as_unicode)
with io.open(self.codepoint105_latin1_filepath, 'w', encoding='latin1') as f:
f.write(self.codepoint105_as_unicode)
with io.open(self.codepoint161_utf8_filepath, 'w', encoding='utf8') as f:
f.write(self.codepoint161_as_unicode)
with io.open(self.codepoint161_latin1_filepath, 'w', encoding='latin1') as f:
f.write(self.codepoint161_as_unicode)
def tearDown(self):
shutil.rmtree(self.tempdir)
def test_encoding_decoding_latin1_utf8(self):
self.assertEqual(u'\xa1', unichr(161))
self.assertEqual(
unichr(161).encode('utf-8').decode('utf-8'),
unichr(161))
self.assertEqual(
unichr(161).encode('latin-1').decode('latin-1'),
unichr(161))
self.assertNotEqual(
unichr(161).encode('utf-8').decode('latin-1'),
unichr(161))
with self.assertRaises(UnicodeDecodeError):
unichr(161).encode('latin-1').decode('utf-8'),
def test_bif_open_read(self):
with open(self.codepoint161_utf8_filepath) as f:
text = f.read()
self.assertEqual(text, self.codepoint161_as_bytestring_utf8)
self.assertEqual(type(text), type(self.codepoint161_as_bytestring_utf8))
with open(self.codepoint161_latin1_filepath) as f:
text = f.read()
self.assertEqual(text, self.codepoint161_as_bytestring_latin1)
self.assertEqual(type(text), type(self.codepoint161_as_bytestring_latin1))
def test_io_open_utf8_read(self):
with io.open(self.codepoint161_utf8_filepath, encoding='utf-8') as f:
text = f.read()
self.assertEqual(text, self.codepoint161_as_unicode)
self.assertEqual(type(text), type(self.codepoint161_as_unicode))
with io.open(self.codepoint161_latin1_filepath, encoding='utf-8') as f:
with self.assertRaises(UnicodeDecodeError):
f.read()
def test_io_open_latin1_read(self):
with io.open(self.codepoint161_utf8_filepath, encoding='latin-1') as f:
text = f.read()
self.assertNotEqual(text, self.codepoint161_as_unicode)
with io.open(self.codepoint161_latin1_filepath, encoding='latin-1') as f:
text = f.read()
self.assertEqual(text, self.codepoint161_as_unicode)
self.assertEqual(type(text), type(self.codepoint161_as_unicode))
def test_bif_open_write(self):
with open('test.txt', 'w') as f:
f.write(self.codepoint105_as_bytestring)
f.write(self.codepoint105_as_unicode)
f.write(self.codepoint161_as_bytestring_utf8)
f.write(self.codepoint161_as_bytestring_latin1)
with self.assertRaises(UnicodeEncodeError):
f.write(self.codepoint161_as_unicode)
def test_io_open_write(self):
with io.open('test.txt', 'w') as f:
f.write(self.codepoint105_as_unicode)
f.write(self.codepoint161_as_unicode)
with self.assertRaises(TypeError):
f.write(self.codepoint105_as_bytestring)
with self.assertRaises(TypeError):
f.write(self.codepoint161_as_bytestring_utf8)
with self.assertRaises(TypeError):
f.write(self.codepoint161_as_bytestring_latin1)
def test_io_open_utf8_write(self):
with io.open('test.txt', 'w', encoding='utf-8') as f:
f.write(self.codepoint105_as_unicode)
f.write(self.codepoint161_as_unicode)
with self.assertRaises(TypeError):
f.write(self.codepoint105_as_bytestring)
with self.assertRaises(TypeError):
f.write(self.codepoint161_as_bytestring_utf8)
with self.assertRaises(TypeError):
f.write(self.codepoint161_as_bytestring_latin1)
def test_io_open_latin1_write(self):
with io.open('test.txt', 'w', encoding='latin-1') as f:
f.write(self.codepoint105_as_unicode)
f.write(self.codepoint161_as_unicode)
with self.assertRaises(TypeError):
f.write(self.codepoint105_as_bytestring)
with self.assertRaises(TypeError):
f.write(self.codepoint161_as_bytestring_utf8)
with self.assertRaises(TypeError):
f.write(self.codepoint161_as_bytestring_latin1)</pre>
<h4 id="references">References / See Also</h4>
<ul>
<li><a href="http://www.b-list.org/weblog/2007/nov/10/unicode/">
James Bennett's Unicode in the real world (2007)</a></li>
<li><a href="http://docs.djangoproject.com/en/dev/ref/unicode/">
Django Unicode documentation</a></li>
<li><a href="http://www.joelonsoftware.com/articles/Unicode.html">
Joel Spolsky's “The Absolute Minimum Every Software Developer
Absolutely, Positively Must Know About Unicode and Character
Sets (No Excuses!)” (2003)</a></li>
<li>Wikipedia on <a href="http://en.wikipedia.org/wiki/Unicode">
Unicode</a>, <a href="http://en.wikipedia.org/wiki/UTF-8">
UTF-8</a>, <a href="http://en.wikipedia.org/wiki/Ascii">
ASCII</a>, <a href="http://en.wikipedia.org/wiki/Character_encoding">
character encoding</a>.</li>
<li><a href="http://docs.python.org/howto/unicode.html">
Python Unicode HOWTO</a></li>
<li><a href="http://lobstertech.com/python_unicode.html">
Making Sense of Python Unicode (2009)
</a></li>
<li><a href="http://pythonconquerstheuniverse.wordpress.com/2012/02/01/unicode-for-dummies-encoding/">
Unicode for dummies — Encoding (2012)
</a></li>
<li><a href="http://packages.python.org/kitchen/unicode-frustrations.html">
Overcoming frustration: Correctly using unicode in python2</a></li>
<li><a href="http://nedbatchelder.com/text/unipain.html">
Ned Batchelder: Pragmatic Unicode (2012)</a></li>
</ul>
Notes on Python deployment using Fabric
2008-09-28T00:24:21-07:00https://www.saltycrane.com/blog/2008/09/notes-python-deployment-using-fabric/<p>I found out about
<a href="http://www.nongnu.org/fab/">Fabric</a> via Armin Ronacher's article
<a href="http://lucumr.pocoo.org/cogitations/2008/07/17/deploying-python-web-applications/">
Deploying Python Web Applications</a>.
Fabric is a
<a href="http://www.capify.org/">Capistrano</a> inspired
deployment tool for the Python community. It is very simple
to use. There are 4 main commands: <code>local</code> is
almost like <code>os.system</code> because it runs a command
on the local machine, <code>run</code> and <code>sudo</code>
run a command on a remote machine as either a normal user
or as root, and <code>put</code> transfers a file to a remote
machine.</p>
<p>Here is a sample setup which displays information about
the Apache processes on my remote EC2 instance.
</p>
<ul>
<li><a href="http://www.saltycrane.com/blog/2007/01/how-to-install-easy-install-for-python/">
Install Easy Install</a></li>
<li>Install Fabric
<pre>$ sudo easy_install Fabric</pre></li>
<li>Create a file called <code>fabfile.py</code> located at <code>~/myproject</code>
<pre class="python">def ec2():
set(fab_hosts = ['ec2-65-234-55-183.compute-1.amazonaws.com'],
fab_user = 'sofeng',
fab_password = 'mypassword',)
def ps_apache():
run("ps -e -O rss,pcpu | grep apache")</pre>
Note: for security reasons, you can remove the password from the fabfile and
Fabric will prompt for it interactively. Per
<a href="http://www.nongnu.org/fab/user_guide.html">the documentation</a>,
Fabric also supports key-based authentication.<br><br>
</li>
<li>Run it
<pre>$ cd ~/myproject
$ fab ec2 ps_apache</pre>
Results:
<pre> Fabric v. 0.0.9, Copyright (C) 2008 Christian Vest Hansen.
Fabric comes with ABSOLUTELY NO WARRANTY; for details type `fab warranty'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `fab license' for details.
Running ec2...
Running ps_apache...
Logging into the following hosts as sofeng:
ec2-65-234-55-183.compute-1.amazonaws.com
[ec2-65-234-55-183.compute-1.amazonaws.com] run: ps -e -O rss,pcpu | grep apache
[ec2-65-234-55-183.compute-1.amazonaws.com] out: 2163 5504 0.0 S ? 00:00:00 /usr/sbin/apache2 -k start
[ec2-65-234-55-183.compute-1.amazonaws.com] out: 2520 15812 0.0 S ? 00:00:00 /usr/sbin/apache2 -k start
[ec2-65-234-55-183.compute-1.amazonaws.com] out: 2521 3664 0.0 S ? 00:00:00 /usr/sbin/apache2 -k start
[ec2-65-234-55-183.compute-1.amazonaws.com] out: 2522 3664 0.0 S ? 00:00:00 /usr/sbin/apache2 -k start
[ec2-65-234-55-183.compute-1.amazonaws.com] out: 2523 3664 0.0 S ? 00:00:00 /usr/sbin/apache2 -k start
[ec2-65-234-55-183.compute-1.amazonaws.com] out: 2524 3664 0.0 S ? 00:00:00 /usr/sbin/apache2 -k start
[ec2-65-234-55-183.compute-1.amazonaws.com] out: 2619 3664 0.0 S ? 00:00:00 /usr/sbin/apache2 -k start
[ec2-65-234-55-183.compute-1.amazonaws.com] out: 2629 1204 0.0 R ? 00:00:00 /bin/bash -l -c ps -e -O rss,pcpu | grep apache
Done.</pre>
</li>
</ul>
Django Blog Project #16: Adding URL redirects using the Blogger API
2008-09-12T22:53:36-07:00https://www.saltycrane.com/blog/2008/09/django-blog-project-16-adding-url-redirects-using-blogger-api/<p>I wanted to insert URL redirects on my old Blogger posts pointing to my new
blog articles.
A <a href="http://www.saltycrane.com/blog/2008/07/django-blog-project-9-migrating-blogger/#lc96">comment</a>
on my <a href="http://www.saltycrane.com/blog/2008/07/django-blog-project-9-migrating-blogger/">Migrating
Blogger Posts</a> post suggested that I use the
<a href="http://code.google.com/apis/blogger/developers_guide_python.html">(Python)
Blogger API</a>. This was a great suggestion. The Blogger API was well documented
and easy to use. Here is the script I used to insert the URL redirects on each
of my old Blogger posts.</p>
<pre class="python">from gdata import service
import re
import gdata
import atom
NEW_HTML = """
<script language='javascript'>
setTimeout('location.href="%s"', 2000);
</script>
<br><br>
<b>
<p>This is my OLD blog. I've copied this post over to my NEW blog at:</p>
<p><a href="%s">%s</a></p>
<p>You should be redirected in 2 seconds.</p>
</b>
<br><br>
"""
# authenticate
blogger_service = service.GDataService('myusername@gmail.com', 'mypassword')
blogger_service.service = 'blogger'
blogger_service.account_type = 'GOOGLE'
blogger_service.server = 'www.blogger.com'
blogger_service.ProgrammaticLogin()
# get list of blogs
query = service.Query()
query.feed = '/feeds/default/blogs'
feed = blogger_service.Get(query.ToUri())
# get blog id
blog_id = feed.entry[0].GetSelfLink().href.split("/")[-1]
# get all posts
query = service.Query()
query.feed = '/feeds/%s/posts/default' % blog_id
query.published_min = '2000-01-01'
query.published_max = '2009-01-01'
query.max_results = 1000
feed = blogger_service.Get(query.ToUri())
print feed.title.text
for entry in feed.entry:
# create link to article on new blog
new_link = re.sub(r'http://iwiwdsmi\.blogspot\.com/(.*)\.html',
r'http://www.saltycrane.com/blog/\1/',
entry.link[0].href)
print new_link
# update post
to_add = NEW_HTML % (new_link, new_link, new_link)
entry.content.text = to_add + entry.content.text
blogger_service.Put(entry, entry.GetEditLink().href)</pre>
Django Blog Project #15: New site logo
2008-09-11T01:12:50-07:00https://www.saltycrane.com/blog/2008/09/django-blog-project-15-new-site-logo/<p>I now have a new site logo design drawn by my wife, Angela! Doesn't it look great?
My previous logo was a crane picture I had just pulled from the web somewhere. So
it is nice to have a custom logo done for me. Luckily my wife is artistic and didn't
mind drawing it for me. I also made some minor changes to the title block to make things
look a little better up there. Now to figure out how to style the rest of the page.
</p>
<p>I also got a "memory over limit" warning from Webfaction this week. Over the weekend,
I had redirected all my old Blogger posts to this blog, so apparently the small increase in traffic brought to light some of my inefficient code. To help solve the
problem, I switched over to <a href="http://code.google.com/p/django-tagging/">django-tagging</a>.
This eliminated a bunch of my inefficient code and I appear to be within the
memory limits now. There is still another section of code I need to rework, but
this solves the problem for now. Django-tagging is pretty cool-- I haven't quite got everything working correctly, but I will be sure to write some notes on it when I get the time.
</p>
Django Blog Project #14: Running Django 1.0
2008-09-05T00:54:03-07:00https://www.saltycrane.com/blog/2008/09/django-blog-project-14-running-django-10/<p>I'm now running Django 1.0 for this blog. For those who haven't heard, Django 1.0
Final was released yesterday sometime. My last Django update had been shortly after
Beta 2 was released and updating to 1.0 Final didn't require any code changes for
me. (I think everything since then was supposed to be bug fixes.) (I also updated
my work project to 1.0 with no problem.) That's about all the content for this post.
Here are some links:</p>
<p>Here are my notes on my path to 1.0:</p>
<ul>
<li><a href="http://www.saltycrane.com/blog/2008/08/django-blog-project-11-migrate-django-096-svn-trunk/">
Django Blog Project #11: Migrating from Django 0.96 to SVN Trunk</a></li>
<li><a href="http://www.saltycrane.com/blog/2008/09/django-blog-project-13-updating-django-10-beta-2-new-comments-adding-markdown-support/">
Django Blog Project #13: Updating Django 1.0 Beta 2 New Comments and Adding Markdown support
</a></li>
</ul>
<br>
<p>And here are some Django 1.0 links:</p>
<ul>
<li><a href="http://docs.djangoproject.com/en/dev/releases/1.0/">Django 1.0 release
notes</a></li>
<li><a href="http://docs.djangoproject.com/en/dev/releases/1.0-porting-guide/">Django
0.96 to 1.0 Porting Guide</a></li>
<li><a href="http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges">A more
detailed list of backwards incompatible changes</a></li>
<li><a href="http://docs.djangoproject.com/en/dev/">Django 1.0 documentation</a>
(This is a new documentation site)</li>
</ul>
<br>
Django Blog Project #13: Updating Django 1.0 Beta 2 New Comments and Adding Markdown support
2008-09-02T00:40:49-07:00https://www.saltycrane.com/blog/2008/09/django-blog-project-13-updating-django-10-beta-2-new-comments-adding-markdown-support/<p>I've updated to Django 1.0 Beta 2. One of the big items for this release was the
new commenting framework. I had been waiting for this, so I was excited to see it
was finally done.</p>
<p>I also added support for <a href="http://daringfireball.net/projects/markdown/">Markdown</a>
formatting of my comments. I actually could have added this earlier, but I only
recently learned that <a href="http://www.freewisdom.org/projects/python-markdown/Django">Django
has built-in support for Markdown</a>.
<h5>Update URLConf</h5>
<p>When I glanced over the changes for the new commenting framework, I missed this
change and I actually had to Google on my error message. Luckily, someone (I don't
remember where I found it now) had run into the same problem and saved me.</p>
<code>~/src/django/myblogsite/urls.py:</code>
<pre class="diff">--- a/urls.py Thu Aug 21 10:05:20 2008 -0500
+++ b/urls.py Mon Sep 01 22:34:16 2008 -0700
@@ -1,6 +1,6 @@
from django.conf.urls.defaults import *
from django.contrib import admin
-from django.contrib.comments.models import FreeComment
+from django.contrib.comments.models import Comment
from iwiwdsmi.myblogapp.views import *
from iwiwdsmi.feeds import *
from iwiwdsmi.views import *
@@ -19,7 +19,7 @@
(r'^admin/(.*)', admin.site.root),
(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed',
{'feed_dict': feeds}),
- (r'^comments/', include('django.contrib.comments.urls.comments')),
+ (r'^comments/', include('django.contrib.comments.urls')),
(r'^$', rootview),</pre>
<h5>Update the database</h5>
<p>See the Django <a href="http://docs.djangoproject.com/en/dev/ref/contrib/comments/upgrade/#ref-contrib-comments-upgrade">
Upgrading from Django's previous comment system</a> guide for more complete information.</p>
<ul>
<li>I ran
<pre>$ cd ~/src/django/myblogsite
$ python manage.py syncdb</pre>
</li>
<li>Then I entered my sqlite3 shell:
<pre>$ sqlite3 mydatabase.sqlite3</pre>
and pasted the following at the command prompt:
<pre>BEGIN;
INSERT INTO django_comments
(content_type_id, object_pk, site_id, user_name, user_email, user_url,
comment, submit_date, ip_address, is_public, is_removed)
SELECT
content_type_id, object_id, site_id, person_name, '', '', comment,
submit_date, ip_address, is_public, approved
FROM comments_freecomment;
INSERT INTO django_comments
(content_type_id, object_pk, site_id, user_id, user_name, user_email,
user_url, comment, submit_date, ip_address, is_public, is_removed)
SELECT
content_type_id, object_id, site_id, user_id, '', '', '', comment,
submit_date, ip_address, is_public, is_removed
FROM comments_comment;
UPDATE django_comments SET user_name = (
SELECT username FROM auth_user
WHERE django_comments.user_id = auth_user.id
) WHERE django_comments.user_id is not NULL;
UPDATE django_comments SET user_email = (
SELECT email FROM auth_user
WHERE django_comments.user_id = auth_user.id
) WHERE django_comments.user_id is not NULL;
COMMIT;</pre>
then exited:
<pre>.exit</pre>
</li>
</ul>
<h5>Templates</h5>
<p>The rest of the changes were with the templates.</p>
<ul>
<li>I removed my old comments templates:
<pre>rm -rf ~/src/django/myblogsite/templates/comments</pre>
</li>
<li>I copied the new templates:
<pre>cp -r ~/lib/django_trunk/django/contrib/comments ~/src/django/myblogsite/templates</pre>
</li>
<li>I updated <code>~/src/django/myblogsite/templates/listpage.html</code>:
<pre class="diff">--- a/templates/listpage.html Thu Aug 21 10:05:20 2008 -0500
+++ b/templates/listpage.html Mon Sep 01 22:46:34 2008 -0700
@@ -47,7 +47,7 @@
{% endfor %}
|
- {% get_free_comment_count for myblogapp.post post.id as comment_count %}
+ {% get_comment_count for myblogapp.post post.id as comment_count %}
<a href="{{ post.get_absolute_url }}#comments">
{{ comment_count|add:post.lc_count }}
Comment{{ comment_count|add:post.lc_count|pluralize}}</a></pre>
</li>
<li>I updated <code>~/src/django/myblogsite/templates/singlepost.html</code>:
<pre class="diff">--- a/templates/singlepost.html Thu Aug 21 10:05:20 2008 -0500
+++ b/templates/singlepost.html Tue Sep 02 00:44:51 2008 -0700
@@ -1,6 +1,7 @@
{% extends "base.html" %}
{% load comments %}
+{% load markup %}
{% block title %}
{{ main_title }}: {{ post.title }}
@@ -59,8 +60,8 @@
{% endfor %}
<br>
- {% get_free_comment_list for myblogapp.post post.id as comment_list %}
- {% get_free_comment_count for myblogapp.post post.id as comment_count %}
+ {% get_comment_list for myblogapp.post post.id as comment_list %}
+ {% get_comment_count for myblogapp.post post.id as comment_count %}
{% if comment_list %}
<h4>{{ comment_count }}
{% if lc_list %}New {% endif %}
@@ -69,13 +70,19 @@
{% for comment in comment_list %}
<br>
<a name="c{{ comment.id }}" href="#c{{ comment.id }}">#{{ forloop.counter }}</a>
- <b>{{ comment.person_name|escape }}</b> commented,
- on {{ comment.submit_date|date:"F j, Y" }} at {{ comment.submit_date|date:"P" }}:
- {{ comment.comment|escape|urlizetrunc:40|linebreaks }}
+ <b>
+ {% if comment.url %}
+ <a href="{{ comment.url }}">{{ comment.name|escape }}</a>
+ {% else %}
+ {{ comment.name|escape }}
+ {% endif %}
+ </b> commented,
+ on {{ comment.submit_date|date:"F j, Y" }} at {{ comment.submit_date|date:"P" }}:
+ {{ comment.comment|markdown:"safe" }}
{% endfor %}
<br>
<h4>Post a comment</h4>
- {% free_comment_form for myblogapp.post post.id %}
+ {% render_comment_form for post %}
{% endblock %}
</pre>
</li>
</ul>
<h5>Add django.contrib.markup to INSTALLED_APPS</h5>
<p>To use Markdown, I added <code>django.contrib.markup</code> to my
<code>INSTALLED_APPS</code> in <code>settings.py</code>
<code>~/src/django/myblogsite/settings.py</code>:
<pre class="diff"> INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.admin',
'django.contrib.comments',
+ 'django.contrib.markup',
'iwiwdsmi.myblogapp',
)</pre>
<p>That was about it. I messed with the templates a little to try to make things
a little prettier. I'm not completely satisfied yet though. My next step is to
add <a href="http://code.google.com/p/django-openid/">django-openid</a> support.
Later on, I'd also like to add email notification and spam filtering.
</p>
<h5>Error messages:</h5>
<b>got an unexpected keyword argument 'core'</b>
<pre style="height:100px; overflow:auto">Validating models...
Unhandled exception in thread started by <function inner_run at 0x8675ca4>
Traceback (most recent call last):
File "/home/sofeng/lib/python-packages/django/core/management/commands/runserver.py", line 47, in inner_run
self.validate(display_num_errors=True)
File "/home/sofeng/lib/python-packages/django/core/management/base.py", line 122, in validate
num_errors = get_validation_errors(s, app)
File "/home/sofeng/lib/python-packages/django/core/management/validation.py", line 28, in get_validation_errors
for (app_name, error) in get_app_errors().items():
File "/home/sofeng/lib/python-packages/django/db/models/loading.py", line 128, in get_app_errors
self._populate()
File "/home/sofeng/lib/python-packages/django/db/models/loading.py", line 57, in _populate
self.load_app(app_name, True)
File "/home/sofeng/lib/python-packages/django/db/models/loading.py", line 72, in load_app
mod = __import__(app_name, {}, {}, ['models'])
File "/home/sofeng/src/django/mozblog/myblogapp/models.py", line 30, in <module>
class LegacyComment(models.Model):
File "/home/sofeng/src/django/mozblog/myblogapp/models.py", line 32, in LegacyComment
website = models.URLField(core=False)
File "/home/sofeng/lib/python-packages/django/db/models/fields/__init__.py", line 828, in __init__
CharField.__init__(self, verbose_name, name, **kwargs)
TypeError: __init__() got an unexpected keyword argument 'core'</pre>
<p>I removed the <code>core</code> argument from my models. This is an oldforms
related thing that has been removed.
<a href="http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#Removedoldformsvalidatorsandrelatedcode">
See here</a>
</p>
<b>Error while importing URLconf myblogsite.urls': cannot import name FreeComment</b>
<p>Comments have been refactored. See the
<a href="http://docs.djangoproject.com/en/dev/ref/contrib/comments/upgrade/">
Upgrading Guide</a>
</p>
Notes on Django and MySql on Amazon's EC2
2008-08-30T03:08:25-07:00https://www.saltycrane.com/blog/2008/08/notes-django-and-mysql-amazons-ec2/<h5>Install Elasticfox</h5>
<p>Install the Elasticfox Firefox Extension for Amazon EC2:
<a href="http://developer.amazonwebservices.com/connect/entry.jspa?externalID=609">
http://developer.amazonwebservices.com/connect/entry.jspa?externalID=609</a>
</p>
<h5>Set up Amazon EC2 accounts and Elasticfox</h5>
<p>Follow
<a href="http://arope99.blogspot.com/2008/05/getting-started-with-amazon-elastic.html">
Arope's instructions for setting up Amazon EC2 accounts
and Elasticfox</a>. I used the
alestic/ubuntu-8.04-hardy-base-20080628.manifest.xml machine
image.
</p>
<h5>view standard apache page</h5>
<p>In Elasticfox, right-click on your running instance and select
"Copy Public DNS Name to clipboard". Then, paste that address
in your browser. You should see Apache's "It works!" page.
</p>
<h5>ssh into instance</h5>
<p>In Elasticfox, right-click on your running instance and select
"SSH to Public Domain Name"</p>
<h5>install stuff</h5>
<p>Ubuntu Hardy has the following versions:</p>
<ul>
<li>Apache 2.2.8</li>
<li>Mod_python 3.3.1</li>
<li>MySql 5.0.51</li>
<li>Django 0.96.1</li>
</ul>
<br>
<p>On your remote instance, do the following.</p>
<pre># apt-get update
# apt-get install python-django
# apt-get install mysql-server
# apt-get install python-mysqldb
# apt-get install libapache2-mod-python</pre>
<p><em>Update 2008-09-09</em>: The
<a href="http://www.djangoproject.com/documentation/modpython/">Django mod_python
documentation</a> recommends using Apache's
<a href ="http://httpd.apache.org/docs/2.2/mod/prefork.html">prefork MPM</a> as opposed
to the <a href="http://httpd.apache.org/docs/2.2/mod/worker.html">worker MPM</a>. The
worker MPM was installed by default on my Alestic Ubuntu image so I uninstalled it and replaced it
with the prefork version.</p>
<pre># apt-get autoremove --purge apache2-mpm-worker
# apt-get install apache2-mpm-prefork</pre>
<p>To see your current version of Apache, run the command:
<code>apache2 -V</code></p>
<h5>create a django project</h5>
<pre># cd /srv
# django-admin startproject mysite</pre>
<h5>configure django mod_python</h5>
<p>See also Jeff Baier's article:
<a href="http://www.jeffbaier.com/2007/07/26/installing-django-on-an-ubuntu-linux-server/">
Installing Django on an Ubuntu Linux Server</a>
for more information.
</p>
<p>Edit <code>/etc/apache2/httpd.conf</code> and insert the
following:</p>
<pre><location "/">
SetHandler python-program
PythonHandler django.core.handlers.modpython
SetEnv DJANGO_SETTINGS_MODULE mysite.settings
PythonPath "['/srv'] + sys.path"
PythonDebug On
</location></pre>
<h5>restart the apache server</h5>
<pre># /etc/init.d/apache2 restart</pre>
<p>You should see Django's "It Worked!" page.</p>
<h5>Set up a MySql database and user</h5>
<p>Note, use the password you entered when installing MySql</p>
<pre># mysql -u root -p
Enter password:
mysql> CREATE DATABASE django_db;
Query OK, 1 row affected (0.01 sec)
mysql> GRANT ALL ON django_db.* TO 'djangouser'@'localhost' IDENTIFIED BY 'yourpassword';
Query OK, 0 rows affected (0.03 sec)
mysql> quit
Bye</pre>
<h5>Edit the Django database settings</h5>
Edit <code>mysite/settings.py</code>:
<pre>DATABASE_ENGINE = 'mysql' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'.
DATABASE_NAME = 'django_db' # Or path to database file if using sqlite3.
DATABASE_USER = 'djangouser' # Not used with sqlite3.
DATABASE_PASSWORD = 'yourpassword' # Not used with sqlite3.
DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.</pre>
<h5>Do a 'syncdb' to create the database tables</h5>
<pre># cd mysite
# 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
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 'sofeng'):
E-mail address: sofeng@email.com
Password:
Password (again):
Superuser created successfully.
Installing index for auth.Message model
Installing index for auth.Permission model
Loading 'initial_data' fixtures...
No fixtures found.</pre>
<h5>upload a mercurial django project</h5>
<p>on the remote instance, install mercurial:</p>
<pre># apt-get install mercurial</pre>
<p>on your local machine with the mercurial repo, run:</p>
<pre>$ hg clone -e 'ssh -i /home/sofeng/.ec2-elasticfox/id_django-keypair.pem' yourproj ssh://root@yourdns.compute-1.amazonaws.com//srv/yourproj</pre>
where <code>/home/sofeng/.ec2-elasticfox/id_django-keypair.pem</code> is
the private key associated with your instance and
<code>yourdns.compute-1.amazonaws.com</code> is the
public domain name associated with your instance.
<p>back on the remote instance:</p>
<pre># cd /srv/mozblog
# hg update</pre>
<pre># python manage.py syncdb</pre>
<h5>set up apache to serve static files</h5>
<ul>
<li>Create a link to the media files:
<pre># cd /var/www
# ln -s /srv/mozblog/media site_media
# ln -s /usr/share/python-support/python-django/django/contrib/admin/media/ admin_media</pre>
</li>
<li>Edit <code>/etc/apache2/httpd.conf</code>:
<pre><location "/">
SetHandler python-program
PythonHandler django.core.handlers.modpython
SetEnv DJANGO_SETTINGS_MODULE mozblog.settings
PythonPath "['/srv'] + sys.path"
PythonDebug On
</location>
<location "/site_media">
SetHandler None
</location>
<location "/admin_media">
SetHandler None
</location></pre>
</li>
</ul>
<h5>Restart the apache server</h5>
<pre># /etc/init.d/apache2 restart</pre>
<br>
Django Blog Project #12: Adding Pygments syntax highlighting
2008-08-20T14:55:29-07:00https://www.saltycrane.com/blog/2008/08/django-blog-project-12-adding-pygments-syntax-highlighting/<p>I've finally added automatic code highlighting to my blog. It
uses <a href="http://pygments.org/">Pygments</a> to do the
syntax highlighting and
<a href="http://www.crummy.com/software/BeautifulSoup/">Beautiful
Soup</a> to find all the <code><pre></code> blocks
to highlight. I still write my blog posts in HTML, but now
add a <code>class</code> attribute to my <code><pre></code>
tags to specify the Pygments lexer to use. For example,
for python code, I use:</p>
<pre><pre class="python">
import this
def demo():
pass</pre></pre>
<p>Which turns into:</p>
<pre class="python">import this
def demo():
pass</pre>
<p>I bought <a href="http://www.b-list.org/">James Bennett</a>'s book,
<a href="http://www.apress.com/book/view/1590599969">Practical
Django Projects</a> about a month ago and it has good information
about creating a blog with Django. It also documented techniques
for syntax highlighting which I used here. To summarize, I
added a new attribute, called <code>body_highlighted</code> to
my <code>Post</code> model. Then, I added a custom
<code>save()</code> method which parses my original HTML with
Beautiful Soup and highlights it with Pygments.</p>
<h5>Model changes</h5>
<p>Here is the relevant code in
<code>~/src/django/myblogsite/myblogapp/models.py</code>:</p>
<pre class="python">class Post(models.Model):
# ...
body = models.TextField()
body_highlighted = models.TextField(editable=False, blank=True)
def save(self):
self.body_highlighted = self.highlight_code(self.body)
super(Post, self).save()
def highlight_code(self, html):
soup = BeautifulSoup(html)
preblocks = soup.findAll('pre')
for pre in preblocks:
if pre.has_key('class'):
try:
code = ''.join([unicode(item) for item in pre.contents])
code = self.unescape_html(code)
lexer = lexers.get_lexer_by_name(pre['class'])
formatter = formatters.HtmlFormatter()
code_hl = highlight(code, lexer, formatter)
pre.replaceWith(BeautifulSoup(code_hl))
except:
pass
return unicode(soup)
def unescape_html(self, html):
html = html.replace('&lt;', '<')
html = html.replace('&gt;', '>')
html = html.replace('&amp;', '&')
return html</pre>
<p><em>Update 2010-04-09:</em> I added the <code>unescape_html</code>
method so that I could highlight Python code with regular expression
named groups. For example:
<pre class="python">m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)", "Malcolm Reynolds")</pre>
With the new fix in place, I just need to escape the < and > characters
with &lt; and &gt; and the syntax highlighting will display
correctly. Before I made the fix, if I did not escape the characters, BeautifulSoup
would add closing tags to what it thought was my malformed HTML. So
instead of the above, it looked like this:
<pre class="python">m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)", "Malcolm Reynolds")</pre>
If anyone knows of a better solution, please let me know.
</p>
<h5>Update the database</h5>
<ul>
<li>List the SQL commands Django would use:
<pre class="bash">$ cd ~/src/django/myblogsite/
$ python manage.py sqlall myblogapp</pre>
<pre>BEGIN;
CREATE TABLE "myblogapp_post" (
"id" integer NOT NULL PRIMARY KEY,
"author_id" integer NOT NULL REFERENCES "auth_user" ("id"),
"title" varchar(200) NOT NULL,
"slug" varchar(200) NOT NULL,
"date_created" datetime NOT NULL,
"date_modified" datetime NOT NULL,
"tags" varchar(200) NOT NULL,
"body" text NOT NULL,
"body_highlighted" text NOT NULL,
)
;
CREATE INDEX "myblogapp_post_author_id" ON "myblogapp_post" ("author_id");
CREATE INDEX "myblogapp_post_slug" ON "myblogapp_post" ("slug");
COMMIT;</pre>
</li>
<li>Enter the sqlite3 shell:
<pre class="bash">$ sqlite3 mydatabase.sqlite3</pre>
<br>
and enter the following statements:
<pre>sqlite> ALTER TABLE myblogapp_post ADD COLUMN body_highlighted text;
sqlite> .exit</pre>
</li>
</ul>
<h5>Update the template</h5>
<p>Here is the relevant code in
<code>~/src/django/myblogsite/templates/singlepost.html</code>:</p>
<pre class="django"> {% if post.body_highlighted %}
{{ post.body_highlighted|safe }}
{% else %}
{{ post.body|safe }}
{% endif %}</pre>
<h5>Add CSS for Pygments</h5>
<p>One last step is to add the CSS for Pygments.
Here is an excerpt from my <code>~/src/django/myblogsite/media/css/mystyle.css</code>:
</p>
<pre class="css" style="height: 300px; overflow: auto">/* PYGMENTS STYLE */
/* customized */
.c { color: #008040; font-style: italic } /* Comment */
.cm { color: #008040; font-style: italic } /* Comment.Multiline */
.cp { color: #BC7A00 } /* Comment.Preproc */
.c1 { color: #008040; font-style: italic } /* Comment.Single */
.cs { color: #008040; font-style: italic } /* Comment.Special */
.gd { color: grey; text-decoration: line-through } /* Generic.Deleted */
.gi { color: red; } /* Generic.Inserted */
.k { color: #000080; font-weight: bold } /* Keyword */
.kc { color: #000000; font-weight: bold } /* Keyword.Constant */
.kd { color: #000000; font-weight: bold } /* Keyword.Declaration */
.kp { color: #000000 } /* Keyword.Pseudo */
.kr { color: #000000; font-weight: bold } /* Keyword.Reserved */
.kt { color: #000000; font-weight: bold } /* Keyword.Type */
/* original settings */
.err { border: 1px solid #FF0000 } /* Error */
.o { color: #666666 } /* Operator */
.ge { font-style: italic } /* Generic.Emph */
.gr { color: #FF0000 } /* Generic.Error */
.gh { color: #000080; font-weight: bold } /* Generic.Heading */
.go { color: #808080 } /* Generic.Output */
.gp { color: #000080; font-weight: bold } /* Generic.Prompt */
.gs { font-weight: bold } /* Generic.Strong */
.gu { color: #800080; font-weight: bold } /* Generic.Subheading */
.gt { color: #0040D0 } /* Generic.Traceback */
.m { color: #666666 } /* Literal.Number */
.s { color: #BA2121 } /* Literal.String */
.na { color: #7D9029 } /* Name.Attribute */
.nb { color: #008000 } /* Name.Builtin */
.nc { color: #0000FF; font-weight: bold } /* Name.Class */
.no { color: #880000 } /* Name.Constant */
.nd { color: #AA22FF } /* Name.Decorator */
.ni { color: #999999; font-weight: bold } /* Name.Entity */
.ne { color: #D2413A; font-weight: bold } /* Name.Exception */
.nf { color: #0000FF } /* Name.Function */
.nl { color: #A0A000 } /* Name.Label */
.nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
.nt { color: #008000; font-weight: bold } /* Name.Tag */
.nv { color: #19177C } /* Name.Variable */
.ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
.w { color: #bbbbbb } /* Text.Whitespace */
.mf { color: #666666 } /* Literal.Number.Float */
.mh { color: #666666 } /* Literal.Number.Hex */
.mi { color: #666666 } /* Literal.Number.Integer */
.mo { color: #666666 } /* Literal.Number.Oct */
.sb { color: #BA2121 } /* Literal.String.Backtick */
.sc { color: #BA2121 } /* Literal.String.Char */
.sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
.s2 { color: #BA2121 } /* Literal.String.Double */
.se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
.sh { color: #BA2121 } /* Literal.String.Heredoc */
.si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
.sx { color: #008000 } /* Literal.String.Other */
.sr { color: #BB6688 } /* Literal.String.Regex */
.s1 { color: #BA2121 } /* Literal.String.Single */
.ss { color: #19177C } /* Literal.String.Symbol */
.bp { color: #008000 } /* Name.Builtin.Pseudo */
.vc { color: #19177C } /* Name.Variable.Class */
.vg { color: #19177C } /* Name.Variable.Global */
.vi { color: #19177C } /* Name.Variable.Instance */
.il { color: #666666 } /* Literal.Number.Integer.Long */</pre>
<br>
<p>All pau. Now we should have pretty syntax highlighted code!
(For those keeping track, this is now version 0.1.3 of my blog.)
</p>
Django Blog Project #11: Migrating from Django 0.96 to SVN Trunk
2008-08-04T17:38:03-07:00https://www.saltycrane.com/blog/2008/08/django-blog-project-11-migrate-django-096-svn-trunk/<p>I've been using the Django 0.96 release for this blog, but
I've been thinking about switching to the SVN trunk version
since it is recommended by the Django community.
<a href="http://www.djangoproject.com/weblog/2008/jul/21/10-alpha/">
Django 1.0 alpha was released</a> a couple weeks ago, so now
seems like a good time to migrate. </p>
<p>Here are the changes I had to make. There were suprisingly few
changes required-- probably because I'm not using a lot of
the Django functionality. For a complete list of changes,
see the <a href="http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges">
Backwards-incompatible changes documentation</a>.
</p>
<p>Note, I am using trunk revision 8210. (2 weeks post Alpha).</p>
<h5>Model changes</h5>
<p>The admin definitions have been decoupled from the model
definitions. Also, the <code>prepopulate_from</code> database
field has been moved to the new admin class. See
<a href="http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#Changedprepopulate_fromtobedefinedintheAdminclassnotdatabasefieldclasses">
here</a> for more information. Also <code>maxlength</code>
was changed to <code>max_length</code>.
</p>
<code>~/src/django/myblogsite/myblogapp/models.py</code>:
<pre>import re
from django.db import models
from django.contrib.auth.models import User
<span style="color:red">from django.contrib import admin</span>
class Post(models.Model):
author = models.ForeignKey(User)
title = models.CharField(<span style="color:red">max_length</span>=200)
slug = models.SlugField(<span style="color:red">max_length</span>=200,
<del style="color:grey">prepopulate_from=['title'],</del>
unique_for_month='date_created')
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
tags = models.CharField(max_length=200, help_text="Space separated.")
body = models.TextField()
body_html = models.TextField(editable=False, blank=True)
lc_count = models.IntegerField(default=0, editable=False)
def get_tag_list(self):
return re.split(" ", self.tags)
def get_absolute_url(self):
return "/blog/%d/%02d/%s/" % (self.date_created.year,
self.date_created.month,
self.slug)
def __str__(self):
return self.title
class Meta:
ordering = ["-date_created"]
<del style="color:grey"> class Admin:
pass</del>
<span style="color:red">class PostAdmin(admin.ModelAdmin):
prepopulated_fields = {'slug': ('title',)}
admin.site.register(Post, PostAdmin)</span></pre>
<h5>Modify URLConf</h5>
<p>See <a href="http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#Mergednewforms-adminintotrunk">
here</a> for more information.</p>
<code>~/src/django/myblogsite/urls.py</code>:
<pre>from django.conf.urls.defaults import *
<span style="color:red">from django.contrib import admin</span>
from django.contrib.comments.models import FreeComment
from iwiwdsmi.myblogapp.views import *
from iwiwdsmi.feeds import *
from iwiwdsmi.views import *
<span style="color:red">admin.autodiscover()</span>
feeds = {
'latest': LatestPosts,
}
urlpatterns = patterns(
'',
(r'^site_media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': '/home/sofeng/src/django/iwiwdsmi/media'}),
<del style="color:grey"> (r'^admin/', include('django.contrib.admin.urls')),</del>
<span style="color:red">(r'^admin/(.*)', admin.site.root),</span>
(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed',
{'feed_dict': feeds}),
(r'^comments/', include('django.contrib.comments.urls.comments')),
(r'^myview1/$', myview1),
(r'^$', rootview),
(r'^blog/$', frontpage),
(r'^blog/(\d{4,4})/(\d{2,2})/([\w\-]+)/$', singlepost),
(r'^blog/(\d{4,4})/$', yearview),
(r'^blog/(\d{4,4})/(\d{2,2})/$', monthview),
(r'^blog/tag/([\w\-]+)/$', tagview),
)</pre>
<h5>Use the <code>safe</code> template filter</h5>
<p>In the Django templates, Django SVN now escapes HTML by default
to protect against cross-site scripting. To display my HTML
blog posts, I needed to use the <code>safe</code> filter.</p>
Excerpt from <code>~/src/django/myblogsite/templates/listpage.html</code>:
<pre> {{ post.body<span style="color:red">|safe</span>|truncatewords_html:"50" }}</pre>
<h5>Use new admin templates</h5>
<p>Finally, I copied over the new Django SVN templates from
<code>trunk/django/contrib/admin/templates</code>.
Django Blog Project #10: Adding support for multiple authors
2008-08-01T12:38:01-07:00https://www.saltycrane.com/blog/2008/08/django-blog-project-10-adding-support-multiple-authors/<p>Here is a quick post on how I added support for multiple users on my blog.</p>
<h5>Modfiy the model</h5>
Excerpt from <code>~/src/django/myblogsite/myblogapp/models.py</code>:
<pre>import re
from django.db import models
<span style="color:red">from django.contrib.auth.models import User</span>
class Post(models.Model):
<span style="color:red">author = models.ForeignKey(User)</span>
title = models.CharField(maxlength=200)
slug = models.SlugField(maxlength=200,
prepopulate_from=['title'],
unique_for_month='date_created')
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
tags = models.CharField(maxlength=200, help_text="Space separated.")
body = models.TextField()
body_html = models.TextField(editable=False, blank=True)
lc_count = models.IntegerField(default=0, editable=False)
def get_tag_list(self):
return re.split(" ", self.tags)
def get_absolute_url(self):
return "/blog/%d/%02d/%s/" % (self.date_created.year,
self.date_created.month,
self.slug)
def __str__(self):
return self.title
class Meta:
ordering = ["-date_created"]
class Admin:
pass</pre>
<h5>Update the database</h5>
<ul>
<li>List the SQL commands Django would use the create the database tables:
<pre>$ cd ~/src/django/myblogsite/
$ python manage.py sqlall myblogapp</pre>
<pre>BEGIN;
CREATE TABLE "myblogapp_post" (
"id" integer NOT NULL PRIMARY KEY,
"author_id" integer NOT NULL REFERENCES "auth_user" ("id"),
"title" varchar(200) NOT NULL,
"slug" varchar(200) NOT NULL,
"date_created" datetime NOT NULL,
"date_modified" datetime NOT NULL,
"tags" varchar(200) NOT NULL,
"body" text NOT NULL,
"body_html" text NOT NULL,
"lc_count" integer NOT NULL
);
CREATE INDEX myblogapp_post_author_id ON "myblogapp_post" ("author_id");
CREATE INDEX myblogapp_post_slug ON "myblogapp_post" ("slug");
COMMIT;</pre>
</li>
<li>Enter the sqlite shell:
<pre>$ sqlite3 mydatabase.sqlite3</pre>
<br>
and enter the following statement:
<pre>sqlite> ALTER TABLE myblogapp_post ADD COLUMN author_id integer REFERENCES auth_user (id);
sqlite> .exit</pre>
</li>
</ul>
<h5>Update the template</h5>
Excerpt from <code>~/src/django/myblogsite/templates/singlepost.html</code>:
<pre> <h3>{{ post.title }}</h3>
{{ post.body }}
<hr>
<div class="post_footer">
<span style="color:red">Author: {{ post.author.first_name }}<br></span>
Date created: {{ post.date_created.date }}<br>
{% ifnotequal post.date_modified.date post.date_created.date %}
Last modified: {{ post.date_modified.date }}<br>
{% endifnotequal %}
Tags:
{% for tag in post.get_tag_list %}
<a href="/blog/tag/{{ tag }}/">{{ tag }}</a>{% if not forloop.last %}, {% endif %}
{% endfor %}
<br>
<a href="/admin/myblogapp/post/{{ post.id }}">Edit post</a>
</div></pre>
<p>Now you should be able to go in to the Admin interface select a
user to associate with each post. Unfortunately, it does not automatically
associate the logged in user with the post.</p>
<p>Here is a snapshot screenshot of what I'm calling version 0.1.1.
Yeah, I know, I skipped 0.1.0-- I consider that to be the point
where I said
<a href="http://www.saltycrane.com/blog/2008/07/transition-bye-blogger-boing/">
Goodbye Blogger</a> and
<a href="http://www.saltycrane.com/blog/2008/07/hello-saltycrane/">Hello
Saltycrane</a>.
</p>
<img align="center" src="/site_media/image/version0.1.1.png">
How to set up Django with MySql on Ubuntu Hardy
2008-07-30T15:09:21-07:00https://www.saltycrane.com/blog/2008/07/how-set-django-mysql-ubuntu-hardy/<p>Here are my notes on installing Django with MySql. Almost all
of this was taken from Zeth's article:
<a href="http://commandline.org.uk/python/2008/jan/25/baby-steps-with-django-part-2-database-setup/">
Baby Steps with Django - part 2 database setup</a>.
</p>
<h5>Install Django and MySql</h5>
<p>Note: during the installation of mysql-server, you will be
prompted for a root password. Use this in the section below.
</p>
<pre>$ sudo apt-get install python-django
$ sudo apt-get install mysql-server
$ sudo apt-get install python-mysqldb</pre>
<h5>Set up a MySql database and user</h5>
<p>Note, use the password you entered when installing MySql</p>
<pre>$ mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 5.0.51a-3ubuntu5.1 (Ubuntu)
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql> CREATE DATABASE django_db;
Query OK, 1 row affected (0.01 sec)
mysql> GRANT ALL ON django_db.* TO 'djangouser'@'localhost' IDENTIFIED BY 'mypassword';
Query OK, 0 rows affected (0.03 sec)
mysql> quit
Bye</pre>
<h5>Create a Django Project</h5>
<pre>$ django-admin startproject mysite</pre>
<h5>Edit the Django database settings</h5>
Edit <code>mysite/settings.py</code>:
<pre>DATABASE_ENGINE = 'mysql' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'.
DATABASE_NAME = 'django_db' # Or path to database file if using sqlite3.
DATABASE_USER = 'djangouser' # Not used with sqlite3.
DATABASE_PASSWORD = 'mypassword' # Not used with sqlite3.
DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.</pre>
<h5>Use Django to create the database tables</h5>
<pre>$ cd mysite
$ 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
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 'sofeng'):
E-mail address: sofeng@email.com
Password:
Password (again):
Superuser created successfully.
Installing index for auth.Message model
Installing index for auth.Permission model
Loading 'initial_data' fixtures...
No fixtures found.</pre>
<h5>Run the development server</h5>
<pre>$ python manage.py runserver
Validating models...
0 errors found.
Django version 0.96.1, using settings 'mysite.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
[30/Jul/2008 16:37:23] "GET / HTTP/1.1" 404 2053</pre>
<p>Point your browser at <code>http://127.0.0.1:8000</code> and
you should see the Django It worked! page.</p>
Django Blog Project #9: Migrating Blogger posts with Beautiful Soup
2008-07-14T22:47:00-07:00https://www.saltycrane.com/blog/2008/07/django-blog-project-9-migrating-blogger/<p>Last post, I talked about adding comments to my new sample blog application.
This was about the last basic feature I needed to add before I started actually
using it for real. Of course there are still a number of features I'd like to
add, such as automatic syntax highlighting with Pygments, and incorporating
django-tagging and some more intersting views, not to mention comment moderation.
But I think those will have to wait-- I want to start using my new blog for
real sometime.</p>
<p>So for the past few days, I've been working on my
<a href="http://www.crummy.com/software/BeautifulSoup/">Beautiful Soup</a>
screen scraper script to copy all my Blogger posts over to my new Django blog.
Initial results came quickly (it's pretty cool to see such a huge data dump
after only a few lines of Beautiful Soup'ing) but the details (especially with
the comments) kind of slowed
me down. I've finally got everything copied over to my satisfaction. Below is
the script I used to do it. Note, I realize it's not pretty-- just a one time
use hack. But hopefully someone else doing the same thing might find it useful.
</p>
<pre class="python" style="height: 400px; overflow:auto;">#!/usr/bin/env python
import datetime
import os
import re
import urllib2
from BeautifulSoup import BeautifulSoup
from myblogapp.models import Post, LegacyComment
from django.contrib.comments.models import FreeComment
URL = ''.join([
'http://iwiwdsmi.blogspot.com/search?',
'updated-min=2006-01-01T00%3A00%3A00-08%3A00&'
'updated-max=2009-01-01T00%3A00%3A00-08%3A00&',
'max-results=1000'
])
html = urllib2.urlopen(URL).read()
soup = BeautifulSoup(html)
for post in soup.html.body.findAll('div', {'class': 'post'}):
print
print '--------------------------------------------------------------'
# save the post title and permalink
h3 = post.find('h3', {'class': 'post-title'})
post_href = h3.find('a')['href']
post_title = h3.find('a').string
post_slug = os.path.basename(post_href).rstrip('.html')
print post_slug
print post_href
print post_title
# save the post body
div = post.find('div', {'class': 'post-body'})
[toremove.extract() for toremove in div.findAll('script')]
[toremove.extract() for toremove in div.findAll('span', {'id': 'showlink'})]
[toremove.extract() for toremove in div.findAll('div', {'style': 'clear: both;'})]
[toremove.parent.extract() for toremove in div.findAll(text='#fullpost{display:none;}')]
post_body = ''.join([str(item)
for item in div.contents
]).rstrip()
post_body = re.sub(r"iwiwdsmi\.blogspot\.com/(\d{4}/\d{2}/[\w\-]+)\.html",
r"www.saltycrane.com/blog/\1/",
post_body)
# count number of highlighted code sections
highlight = div.findAll('div', {'class': 'highlight'})
if highlight:
hl_count += len(highlight)
hl_list.append(post_title)
# save the timestamp
a = post.find('a', {'class': 'timestamp-link'})
try:
post_timestamp = a.string
except:
match = re.search(r"\.com/(\d{4})/(\d{2})/", post_href)
if match:
year = match.group(1)
month = match.group(2)
post_timestamp = "%s/01/%s 11:11:11 AM" % (month, year)
print post_timestamp
# save the tags (this is ugly, i know)
if 'error' in post_title.lower():
post_tags = ['error']
else:
post_tags = []
span = post.find('span', {'class': 'post-labels'})
if span:
a = span.findAll('a', {'rel': 'tag'})
else:
a = post.findAll('a', {'rel': 'tag'})
post_tags = ' '.join([tag.string for tag in a] + post_tags)
if not post_tags:
post_tags = 'untagged'
print post_tags
# add Post object to new blog
if True:
p = Post()
p.title = post_title
p.body = post_body
p.date_created = datetime.datetime.strptime(post_timestamp, "%m/%d/%Y %I:%M:%S %p")
p.date_modified = p.date_created
p.tags = post_tags
p.slug = post_slug
p.save()
# check if there are comments
a = post.find('a', {'class': 'comment-link'})
if a:
comm_string = a.string.strip()
else:
comm_string = "0"
if comm_string[0] != "0":
print
print "COMMENTS:"
# get the page with comments
html_single = urllib2.urlopen(post_href).read()
soup_single = BeautifulSoup(html_single)
# get comments
comments = soup_single.html.body.find('div', {'class': 'comments'})
cauth_list = comments.findAll('dt')
cbody_list = comments.findAll('dd', {'class': 'comment-body'})
cdate_list = comments.findAll('span', {'class': 'comment-timestamp'})
if not len(cauth_list)==len(cbody_list)==len(cdate_list):
raise "didn't get all comment data"
for auth, body, date in zip(cauth_list, cbody_list, cdate_list):
# create comment in database
lc = LegacyComment()
lc.body = str(body.p)
# find author
lc.author = "Anonymous"
auth_a = auth.findAll('a')[-1]
auth_no_a = auth.contents[2]
if auth_a.string:
lc.author = auth_a.string
elif auth_no_a:
match = re.search(r"\s*([\w\s]*\w)\s+said", str(auth_no_a))
if match:
lc.author = match.group(1)
print lc.author
# find website
try:
lc.website = auth_a['href']
except KeyError:
lc.website = ''
print lc.website
# other info
lc.date_created = datetime.datetime.strptime(
date.a.string.strip(), "%m/%d/%Y %I:%M %p")
print lc.date_created
lc.date_modified = lc.date_created
lc.post_id = p.id
lc.save()
</pre>
<p>I also made some changes to my Django blog code as I migrated my Blogger posts.
The main addition was a <code>LegacyComment</code> model along with the associated
views and templates. My Blogger comments consisted of HTML markup, but I didn't
want to allow arbitrary HTML in my new comments for fear of
<a href="http://en.wikipedia.org/wiki/Cross-site_scripting">cross site
scripting</a>. So I separated my legacy Blogger comments from my new Django
site comments.</p>
<br /><br /><b>models.py</b><br />
<p>Here are my model changes. I added a <code>LegacyComment</code> class which
contains pertinent comment attributes and a <code>ForeignKey</code> to the
post that it belongs to. I also added a <code>lc_count</code> (for legacy
comment count) field to the <code>Post</code> class which stores the number
of comments for the post. It is updated by the <code>save()</code> method in
the <code>LegacyComment</code> class every time a comment is saved. Hmmm,
I just realized the count will be wrong if I ever edit these comments. Well,
since these are legacy comments, hopefully I won't have to edit them.
</p>
<code>~/src/django/myblogsite/myblogapp/models.py</code>:
<pre style="height:300px; overflow:auto">import re
from django.db import models
class Post(models.Model):
title = models.CharField(maxlength=200)
slug = models.SlugField(maxlength=100)
date_created = models.DateTimeField() #auto_now_add=True)
date_modified = models.DateTimeField()
tags = models.CharField(maxlength=200)
body = models.TextField()
body_html = models.TextField(editable=False, blank=True)
<span style="color:red">lc_count = models.IntegerField(default=0, editable=False)</span>
def get_tag_list(self):
return re.split(" ", self.tags)
def get_absolute_url(self):
return "/blog/%d/%02d/%s/" % (self.date_created.year,
self.date_created.month,
self.slug)
def __str__(self):
return self.title
class Meta:
ordering = ["-date_created"]
class Admin:
pass
<span style="color:red">class LegacyComment(models.Model):
author = models.CharField(maxlength=60)
website = models.URLField(core=False)
date_created = models.DateTimeField()
date_modified = models.DateTimeField()
body = models.TextField()
post = models.ForeignKey(Post)
def save(self):
p = Post.objects.get(id=self.post.id)
p.lc_count += 1
p.save()
super(LegacyComment, self).save()
class Meta:
ordering = ["date_created"]
class Admin:
pass</span>
</pre>
<br /><br /><b>views.py</b><br />
<p>Here is an excerpt from my views.py file showing the changes:</p>
<code>~/src/django/myblogsite/myblogapp/views.py</code>:
<pre>import re
from datetime import datetime
from django.shortcuts import render_to_response
from myblogsite.myblogapp.models import Post, <span style="color:red">LegacyComment</span>
MONTH_NAMES = ('', 'January', 'Feburary', 'March', 'April', 'May', 'June', 'July',
'August', 'September', 'October', 'November', 'December')
MAIN_TITLE = "Sofeng's Blog 0.0.7"
def frontpage(request):
posts, pagedata = init()
<span style="color:red">posts = posts[:5]
pagedata.update({'post_list': posts,
'subtitle': '',})</span>
return render_to_response('listpage.html', pagedata)
def singlepost(request, year, month, slug2):
posts, pagedata = init()
post = posts.get(date_created__year=year,
date_created__month=int(month),
slug=slug2,)
<span style="color:red">legacy_comments = LegacyComment.objects.filter(post=post.id)
pagedata.update({'post': post,
'lc_list': legacy_comments,})</span>
return render_to_response('singlepost.html', pagedata)
</pre>
<br /><br /><b>Templates</b><br />
<p>In the list page template I used the <code>truncatewords_html</code> template
filter to show a 50 word post summary on the list pages instead of the full post.
I also added the legacy comment count with the Django free comment count to
display the total number of comments.</p>
Excerpt from <code>~/src/django/myblogsite/templates/listpage.html</code>:
<pre>{% block main %}
<br>
{% for post in post_list %}
<h4><a href="/blog/{{ post.date_created|date:"Y/m" }}/{{ post.slug }}/">
{{ post.title }}</a>
</h4>
{{ post.body<span style="color:red">|truncatewords_html:"50"</span> }}
<span style="color:red"><a href="{{ post.get_absolute_url }}">Read more...</a><br>
<br></span>
<hr>
<div class="post_footer">
{% ifnotequal post.date_modified.date post.date_created.date %}
Last modified: {{ post.date_modified.date }}<br>
{% endifnotequal %}
Date created: {{ post.date_created.date }}<br>
Tags:
{% for tag in post.get_tag_list %}
<a href="/blog/tag/{{ tag }}/">{{ tag }}</a>{% if not forloop.last %}, {% endif %}
{% endfor %}
<br>
{% get_free_comment_count for myblogapp.post post.id as comment_count %}
<a href="{{ post.get_absolute_url }}<span style="color:red">#comments">
{{ comment_count|add:post.lc_count }}
Comment{{ comment_count|add:post.lc_count|pluralize}}</a></span>
</div>
<br>
{% endfor %}
{% endblock %}
</pre>
<p>In the single post template, I added the display of the Legacy comments in addition
to the Django free comments.</p>
Excerpt from <code>~/src/django/myblogsite/templates/singlepost.html</code>:
<pre> <span style="color:red"><a name="comments"></a>
{% if lc_list %}
<h4>{{ lc_list|length }} Legacy Comment{{lc_list|length|pluralize}}</h4>
{% endif %}
{% for legacy_comment in lc_list %}
<br>
<a name="lc{{ legacy_comment.id }}" href="#lc{{ legacy_comment.id }}">
#{{ forloop.counter }}</a>
{% if legacy_comment.website %}
<a href="{{ legacy_comment.website }}">
<b>{{ legacy_comment.author|escape }}</b></a>
{% else %}
<b>{{ legacy_comment.author|escape }}</b>
{% endif %}
commented,
on {{ legacy_comment.date_created|date:"F j, Y" }}
at {{ legacy_comment.date_created|date:"P" }}:
{{ legacy_comment.body }}
{% endfor %}
<br></span>
</pre>
<p>That's it. Hopefully, I can start using my new blog soon. Please browse around on
the <a href="http://saltycrane.com/blog">new Django site</a> and let me know if you run across any problems. When everything
looks to be OK, I'll start posting only on my new Django site.</p>
<p>Here is a snapshot screenshot of version 0.0.8:</p>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_lZhqNsiakm4/SHw6-e-2w7I/AAAAAAAAAFo/V9SINeq8LVM/s1600-h/version0.0.8.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://bp1.blogger.com/_lZhqNsiakm4/SHw6-e-2w7I/AAAAAAAAAFo/V9SINeq8LVM/s320/version0.0.8.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5223114512985932722" /></a>
<br /><p>The live site can be viewed at: <a href="http://saltycrane.com/blog">http://saltycrane.com/blog</a>
</p>
<br />Related posts:<br />
<a href="http://www.saltycrane.com/blog/2008/05/django-new-blog-project/">
Django Blog Project #1: Creating a basic blog</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/first-django-webfaction-deployment/">
Django Blog Project #2: Deploying at Webfaction</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-3-using-css-and/">
Django Blog Project #3: Using CSS and Template Inheritance</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-4-adding-post/">
Django Blog Project #4: Adding post metadata</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-5-yui-css-and/">
Django Blog Project #5: YUI CSS and serving static media</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-6-creating-standard/">
Django Blog Project #6: Creating standard blog views</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-7-adding-simple/">
Django Blog Project #7: Adding a simple Atom feed</a><br />
<a href="http://www.saltycrane.com/blog/2008/07/django-blog-project8-adding-basic/">
Django Blog Project #8: Adding basic comment functionality</a><br />
<br />
Django Blog Project #8: Adding basic comment functionality with Django Free Comments
2008-07-06T22:32:00-07:00https://www.saltycrane.com/blog/2008/07/django-blog-project8-adding-basic/<p><em>Update 2009-05-08: The notes here apply to Django 0.96 and not
Django 1.0 or later. In particular, <code>FreeComment</code> has been changed
to <code>Comment</code> in Django 1.0. For current documentation, please go
<a href="http://docs.djangoproject.com/en/dev/ref/contrib/comments/#ref-contrib-comments-index">here</a> instead.</em></p>
<p>This post describes how I added basic commenting functionality using Django's
Free Comments. Note, this built-in functionality does not support comment moderation
(used to counteract spam) or other nice features. I tried to use David Bennett's
<a href="http://code.google.com/p/django-comment-utils/">comment_utils</a>, but I
couldn't get it to work. So for now, I'm just going to use Django Free Comments
and hopefully add in moderation and other features later.</p>
<p>I basically followed the instructions on the wiki:
<a href="http://code.djangoproject.com/wiki/UsingFreeComment">
Using Django's Free Comments</a>. See there for more information.
It was pretty easy to set up.</p>
<br /><b>settings.py</b><br />
<p>In my settings.py file, I added <code>django.contrib.comments</code> to the
list of installed apps.</p>
Excerpt from <code>~/src/django/myblogsite/settings.py</code>:
<pre>INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.admin',
<span style="color:red">'django.contrib.comments',</span>
'iwiwdsmi.myblogapp',
)</pre>
<br /><br /><b>urls.py</b><br />
<p>I modified my urls.py to use the <code>comment</code> module's URLConf.</p>
<code>~/src/django/myblogsite/urls.py</code>:
<pre>from django.conf.urls.defaults import *
<span style="color:red">from django.contrib.comments.models import FreeComment</span>
from iwiwdsmi.myblogapp.views import *
from iwiwdsmi.feeds import *
feeds = {
'latest': LatestPosts,
}
urlpatterns = patterns(
'',
(r'^site_media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': '/home/sofeng/src/django/myblogsite/media'}),
(r'^admin/', include('django.contrib.admin.urls')),
(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed',
{'feed_dict': feeds}),
<span style="color:red">(r'^comments/', include('django.contrib.comments.urls.comments')),</span>
(r'^myview1/$', myview1),
(r'^blog/$', frontpage),
(r'^blog/(\d{4})/(\d{2})/([\w\-]+)/$', singlepost),
(r'^blog/(\d{4})/$', yearview),
(r'^blog/(\d{4})/(\d{2})/$', monthview),
(r'^blog/tag/([\w\-]+)/$', tagview),
)</pre>
<br /><br /><b>python manage.py syncdb</b><br />
<p>To install the comments model, I ran <code>python manage.py syncdb</code>:</p>
<pre>$ cd ~/src/django/myblogsite
$ python manage.py syncdb</pre>
<br /><br /><b>List page template</b><br />
<p>Then I modified my list page template to display the number of comments for
each post:</p>
<code>~/src/django/myblogsite/templates/listpage.html</code>:
<pre>{% extends "base.html" %}
<span style="color:red">{% load comments %}</span>
{% block title %}
{{ main_title }}
{% if subtitle %}:{% endif %}
{{ subtitle }}
{% endblock %}
{% block header1 %}
{% if subtitle %}
<a href="/blog/">{{ main_title }}</a>
{% else %}
{{ main_title }}
{% endif %}
{% endblock %}
{% block header2 %}
{{ subtitle }}
{% endblock %}
{% block main %}
{% for post in post_list %}
<h3><a href="/blog/{{ post.date_created|date:"Y/m" }}/{{ post.slug }}/">
{{ post.title }}</a>
</h3>
{{ post.body }}
<hr>
<div class="post_footer">
{% ifnotequal post.date_modified.date post.date_created.date %}
Last modified: {{ post.date_modified.date }}<br>
{% endifnotequal %}
Date created: {{ post.date_created.date }}<br>
Tags:
{% for tag in post.get_tag_list %}
<a href="/blog/tag/{{ tag }}/">{{ tag }}</a>{% if not forloop.last %}, {% endif %}
{% endfor %}
<span style="color:red"><br>
{% get_free_comment_count for myblogapp.post post.id as comment_count %}
<a href="{{ post.get_absolute_url }}">{{ comment_count }} Comment{{ comment_count|pluralize}}</a></span>
</div>
<br>
{% endfor %}
{% endblock %}</pre>
<p>Oh, I forgot to mention, I implemented a <code>get_absolute_url</code>
method in my <code>Post</code> model. This is the preferred way to
specify the url to the detail view of my <code>Post</code> object.
The code for the method is shown below:</p>
<code>~/src/django/myblogsite/myblogapp/models.py</code>:
<pre>import re
from django.db import models
class Post(models.Model):
title = models.CharField(maxlength=200)
slug = models.SlugField(maxlength=100)
date_created = models.DateTimeField() #auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
tags = models.CharField(maxlength=200)
body = models.TextField()
def get_tag_list(self):
return re.split(" ", self.tags)
<span style="color:red">def get_absolute_url(self):
return "/blog/%d/%02d/%s/" % (self.date_created.year,
self.date_created.month,
self.slug)</span>
def __str__(self):
return self.title
class Meta:
ordering = ["-date_created"]
class Admin:
pass</pre>
<br /><br /><b>Detail page template</b><br />
<p>Then I modified my single post, or detail page, template:</p>
<code>~/src/django/myblogsite/templates/singlepost.html</code>:
<pre>{% extends "base.html" %}
<span style="color:red">{% load comments %}</span>
{% block title %}
{{ main_title }}: {{ post.title }}
{% endblock %}
{% block header1 %}
<a href="/blog/">{{ main_title }}</a>
{% endblock %}
{% block main %}
<h3>{{ post.title }}</h3>
{{ post.body }}
<hr>
<div class="post_footer">
{% ifnotequal post.date_modified.date post.date_created.date %}
Last modified: {{ post.date_modified.date }}<br>
{% endifnotequal %}
Date created: {{ post.date_created.date }}<br>
Tags:
{% for tag in post.get_tag_list %}
<a href="/blog/tag/{{ tag }}/">{{ tag }}</a>{% if not forloop.last %}, {% endif %}
{% endfor %}
</div>
<br>
<span style="color:red">{% get_free_comment_list for myblogapp.post post.id as comment_list %}
{% get_free_comment_count for myblogapp.post post.id as comment_count %}
{% if comment_list %}
<h4>{{ comment_count }} Comment{{ comment_count|pluralize}}</h4>
{% endif %}
{% for comment in comment_list %}
<a href="#c{{ comment.id }}">#{{ forloop.counter }}</a>
<b>{{ comment.person_name|escape }}</b> commented,
on {{ comment.submit_date|date:"F j, Y" }} at {{ comment.submit_date|date:"P" }}:
{{ comment.comment|escape|urlizetrunc:40|linebreaks }}
{% endfor %}
<br>
<h4>Post a comment</h4>
{% free_comment_form for myblogapp.post post.id %}</span>
{% endblock %}</pre>
<br /><br /><b>Create templates for comment preview and comment posted</b><br />
<p>At this point, I can view these template changes on my blog list view
or detail view. However, if I try to add a comment, I will get a
<code>TemplateDoesNotExist</code> exception. I need two new templates--
one for the comment preview and one for the page just after the comment
is posted. I just copied these from <a href="http://code.djangoproject.com/wiki/UsingFreeComment">the
wiki</a>. Probably, I should dress these up a little to match my site.
For now, I'll use the generic ones. I put them in a new directory
called <code>comments</code> in my <code>templates</code> directory.
</p>
<code>~/src/django/myblogsite/templates/comments/free_preview.html</code>:
<pre style="height: 200px; overflow:auto; color:red"><h1>Preview your comment</h1>
<form action="/comments/postfree/" method="post">
{% if comment_form.has_errors %}
<p><strong style="color: red;">Please correct the following errors.</strong></p>
{% else %}
<div class="comment">
{{ comment.comment|escape|urlizetrunc:"40"|linebreaks }}
<p class="date small">Posted by <strong>{{ comment.person_name|escape }}</strong></p>
</div>
<p><input type="submit" name="post" value="Post public comment" /></p>
<h1>Or edit it again</h1>
{% endif %}
{% if comment_form.person_name.errors %}
{{ comment_form.person_name.html_error_list }}
{% endif %}
<p><label for="id_person_name">Your name:</label> {{ comment_form.person_name }}</p>
{% if comment_form.comment.errors %}
{{ comment_form.comment.html_error_list }}
{% endif %}
<p>
<label for="id_comment">Comment:</label>
<br />
{{ comment_form.comment }}
</p>
<input type="hidden" name="options" value="{{ options }}" />
<input type="hidden" name="target" value="{{ target }}" />
<input type="hidden" name="gonzo" value="{{ hash }}" />
<p>
<input type="submit" name="preview" value="Preview revised comment" />
</p>
</form></pre>
<br /><code>~/src/django/myblogsite/templates/comments/posted.html</code>:
<pre style="color:red"><h1>Comment posted successfully</h1>
<p>Thanks for contributing.</p>
{% if object %}
<ul>
<li><a href="{{ object.get_absolute_url }}">View your comment</a></li>
</ul>
{% endif %}</pre>
<br /><br /><b>Add some comments</b><br />
<p>I'm done. I added some comments and saw them show up on my page.</p>
<br /><br /><b>Upload, update, restart server</b><br />
<p>I uploaded to webfaction, updated my Mercurial repository, and
restarted the Apache server. Everything's good.</p>
<p>Here is a snapshot screenshot of a detail page with some comments:</p>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_lZhqNsiakm4/SHGppgh6d7I/AAAAAAAAAFg/O9zxZBvv-lg/s1600-h/version0.0.7.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://bp3.blogger.com/_lZhqNsiakm4/SHGppgh6d7I/AAAAAAAAAFg/O9zxZBvv-lg/s320/version0.0.7.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5220139973671024562" /></a>
<br /><p>The live site can be viewed at:
<a href="http://saltycrane.com/blog/">http://saltycrane.com/blog</a></p>
<br />Related posts:<br />
<a href="http://www.saltycrane.com/blog/2008/05/django-new-blog-project/">
Django Blog Project #1: Creating a basic blog</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/first-django-webfaction-deployment/">
Django Blog Project #2: Deploying at Webfaction</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-3-using-css-and/">
Django Blog Project #3: Using CSS and Template Inheritance</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-4-adding-post/">
Django Blog Project #4: Adding post metadata</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-5-yui-css-and/">
Django Blog Project #5: YUI CSS and serving static media</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-6-creating-standard/">
Django Blog Project #6: Creating standard blog views</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-7-adding-simple/">
Django Blog Project #7: Adding a simple Atom feed</a><br />
<br />
Django Blog Project #7: Adding a simple Atom feed
2008-06-30T00:54:00-07:00https://www.saltycrane.com/blog/2008/06/django-blog-project-7-adding-simple/<p>Setting up a basic Atom feed for my new blog was not too difficult. I
followed the instructions in the Django
documentation and created a basic Atom feed for my 10 latest posts. There
were a couple of things I couldn't figure out: 1) how to create a feed
for a specific tag, and 2) how to redirect my feed to FeedBurner. I know
these things are not that hard, but I want to get my new blog working
as soon as possible so I can start using it. If anyone knows how
to do these things, I'd be grateful for your feedback. Regarding item 1,
I plan to replace my tagging functionality hack with the
<a href="http://code.google.com/p/django-tagging/">django-tagging</a> application
so maybe I will wait until then to add tag-specific feeds. Regarding item
2, I read about using .htaccess to do redirects, but I couldn't figure
out how to do a feed redirect at Webfaction.</p>
<p>Anyways, here are the steps I took to create a basic feed of my latests posts.
Refer to the <a href="http://www.djangoproject.com/documentation/0.96/syndication_feeds/#a-complex-example">Django
syndication feed framework documentation</a> for more details.
</p>
<br /><br /><strong>Modify the URLConf</strong><br />
Per the documentation, I modified my <code>~/src/django/myblogsite/urls.py</code>:
<pre>from django.conf.urls.defaults import *
from iwiwdsmi.myblogapp.views import *
from iwiwdsmi.feeds import *
<span style="color:red">feeds = {
'latest': LatestPosts,
}</span>
urlpatterns = patterns(
'',
(r'^site_media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': '/home/sofeng/src/django/myblogsite/media'}),
(r'^admin/', include('django.contrib.admin.urls')),
<span style="color:red">(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed',
{'feed_dict': feeds}),</span>
(r'^blog/$', frontpage),
(r'^blog/(\d{4,4})/(\d{2,2})/([\w\-]+)/$', singlepost),
(r'^blog/(\d{4,4})/$', yearview),
(r'^blog/(\d{4,4})/(\d{2,2})/$', monthview),
(r'^blog/tag/([\w\-]+)/$', tagview),
)</pre>
<br /><br /><strong>Create a <code>feeds.py</code> file</strong><br />
<p>I created the following file at <code>~/src/django/myblogsite/feeds.py</code>. Note,
I factored out my main title, "Sofeng's Blog 0.0.6", into the variable
<code>MAIN_TITLE</code> in my <code>views.py</code> file and used it here.
The class attributes, <code>title</code> and <code>description</code> are
strings describing the feed.</p>
<pre>from django.contrib.syndication.feeds import Feed
from django.utils.feedgenerator import Atom1Feed
from iwiwdsmi.myblogapp.models import Post
from iwiwdsmi.myblogapp.views import MAIN_TITLE
class LatestPosts(Feed):
title = MAIN_TITLE
link = "/blog/"
description = MAIN_TITLE + ": Latest posts"
feed_type = Atom1Feed
def items(self):
return Post.objects.order_by('-date_created')[:10]</pre>
<br /><br /><strong>Create feed templates</strong><br />
<p>I created two feed templates which are used to display the title and body
for each post. They each only contain one line.</p>
<code>~/src/django/myblogsite/templates/feeds/latest_title.html</code>:
<pre>{{ obj.title }}</pre>
<br /><code>~/src/django/myblogsite/templates/feeds/latest_description.html</code>:
<pre>{{ obj.body }}</pre>
<br /><br /><strong>Display a link to the new feed</strong><br />
<p>I added a link to my new feed in my <code>base.html</code>
template.</p>
Excerpt from <code>~/src/django/myblogsite/templates/base.html</code>:
<pre> <h4>FEEDS</h4>
<a href="/feeds/latest/" rel="alternate" type="application/rss+xml"><img alt="" style="vertical-align:middle;border:0" src="http://www.feedburner.com/fb/images/pub/feed-icon16x16.png"/></a>
<a href="/feeds/latest/">Subscribe to Atom feed</a><br></pre>
<br /><br /><strong>Change site name</strong><br />
<p>When I first created my Django project, I installed the
<code>django.contrib.sites</code> app (by default), but I did not change
my site name, so my feed used the site <code>example.com</code> instead
of my real site. Here is how I changed my site name:</p>
<ol>
<li>I went to the admin page at
<a href="http://127.0.0.1:8000/admin/">http://127.0.0.1:8000/admin/</a>
</li>
<li>I clicked on "Sites", then on "example.com".</li>
<li>I changed "example.com" to "saltycrane.com" and clicked the "Save" button.</li>
</ol>
<br /><br /><strong>Deploy and Test</strong><br />
<p>I pushed my changes to the Webfaction server, updated my repository,
and restarted the Apache server. Then I pointed Firefox at
<a href="http://saltycrane.com/blog/">http://saltycrane.com/blog/</a>
and clicked on my new "Subscribe to Atom feed" link. I subscribed to the
feed using "Google" and chose "Add to Google Reader" and saw my new feed
in <a href="http://www.google.com/reader/">Google Reader</a>.
<br />Here is a screenshot of my feed in my Google Reader:<br />
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_lZhqNsiakm4/SGiTFN4xkeI/AAAAAAAAAFY/as6MFBgHumI/s1600-h/version0.0.6_in_greader.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://bp3.blogger.com/_lZhqNsiakm4/SGiTFN4xkeI/AAAAAAAAAFY/as6MFBgHumI/s320/version0.0.6_in_greader.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5217581886145139170" /></a>
<br /><br />Extra links on redirection for my reference:<br />
<a href="https://help.webfaction.com/index.php?_m=knowledgebase&_a=viewarticle&kbarticleid=30&nav=0,19">Webfaction
knowledgebase article on redirection</a><br />
<a href="http://www.456bereastreet.com/archive/200608/redirecting_feeds_to_feedburner/">Blog
article on redirecting feeds to FeedBurner</a><br />
<br />Related posts:<br />
<a href="http://www.saltycrane.com/blog/2008/05/django-new-blog-project/">
Django Blog Project #1: Creating a basic blog</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/first-django-webfaction-deployment/">
Django Blog Project #2: Deploying at Webfaction</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-3-using-css-and/">
Django Blog Project #3: Using CSS and Template Inheritance</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-4-adding-post/">
Django Blog Project #4: Adding post metadata</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-5-yui-css-and/">
Django Blog Project #5: YUI CSS and serving static media</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-6-creating-standard/">
Django Blog Project #6: Creating standard blog views</a><br />
<a href="http://www.saltycrane.com/blog/2008/07/django-blog-project8-adding-basic/">
Django Blog Project #8: Adding basic comment functionality with Django Free Comments</a><br />
<br />
</p>
Django Blog Project #6: Creating standard blog views
2008-06-28T00:21:00-07:00https://www.saltycrane.com/blog/2008/06/django-blog-project-6-creating-standard/<p>My <a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-5-yui-css-and/">
last django post</a> actually got a little bit of publicity-- a mention on
<a href="http://www.reddit.com/r/Python/">Python reddit</a>, and a mention in the
<a href="http://yuiblog.com/blog">Yahoo User Interface blog</a>
<a href="http://yuiblog.com/blog/2008/06/20/wild-20080620/">
<em>In the Wild for June 20</em></a> article. So I feel a little pressure
to produce intelligent, engaging content so as not to disappoint the hordes of
new readers I must now have. Unfortunately, the topic of this post is pretty dry.
Goodbye, new readers. I'll try to find you another time.</p>
<p>The topic of this post is creating standard blog views. So far, I've created
a simple blog application that allows me to log in to an Admin interface and
enter blog posts, I added some post metadata such as date and tags, and, last
time, I added YUI stylesheets to create a cross browser layout solution.
However, until now, my trying-so-hard-to-be-a-real website only had a single
front page view with a list of all the blog posts. I needed to add other views,
such as a single post view, an archive view, and a tag view. Here are the changes
I made to implement these views.</p>
<p><em>Warning</em>: I am a newbie to Django and web development. I've created
this blog series to document my journey in creating a blog application using Django.
It does not necessarily represent Django best practices. To the extent of my
knowledge, I try to follow Django conventions and philosophies, but I know
I must still have a number of violations, particularly in this latest revision.
Hopefully, as I learn better ways of doing things, I can post corrections. If
you have corrections, please leave a comment.
</p>
<p>For a much better example of a Django blog application, see
<a href="http://adam.gomaa.us/">Adam Gomaa</a>'s Django blog site
<a href="http://hg.gomaa.us/agdj/">source code repository</a>. Though
I don't understand everything in there, I've referenced it a number of
times while creating my blog site. <em>Update 2008-06-29</em>: I just
discovered that <a href="http://www.b-list.org/about/">James Bennett</a>,
a Django contributer, also has
<a href="http://code.google.com/p/coltrane-blog/source/browse">blog
application source code available online</a>. Seeing as he
has <a href="http://www.apress.com/book/view/1590599969">written a
book on Django</a>, this <sarcasm>might</sarcasm> be a
good place to look as well.
</p>
<br /><br /><strong><code>models.py</code></strong><br />
<p>In my <code>models.py</code> file, I added a slug field to store the
last part of the url and a method called <code>get_tag_list</code>
to return the tags for the post as a list.</p>
<code>~/src/django/myblogsite/myblogapp/models.py</code>:
<pre>import re
from django.db import models
class Post(models.Model):
title = models.CharField(maxlength=200)
slug = models.SlugField(maxlength=100)
date_created = models.DateTimeField()
date_modified = models.DateTimeField(auto_now=True)
tags = models.CharField(maxlength=200)
body = models.TextField()
def get_tag_list(self):
return re.split(" ", self.tags)
def __str__(self):
return self.title
class Meta:
ordering = ["-date_created"]
class Admin:
pass</pre>
<br /><br /><strong><code>views.py</code></strong><br />
<p>In my <code>views.py</code> file, to the frontpage view, I added a
single post view, an archive view by year, an archive view by month,
and a tag view. I also added a couple functions to create an archive
index and a tag index.</p>
<code>~/src/django/myblogsite/myblogapp/views.py</code>:
<pre style="height: 200px; overflow: auto;">import re
from datetime import datetime
from django.shortcuts import render_to_response
from myblogsite.myblogapp.models import Post
MONTH_NAMES = ('', 'January', 'Feburary', 'March', 'April', 'May', 'June', 'July',
'August', 'September', 'October', 'November', 'December')
def frontpage(request):
posts, pagedata = init()
pagedata.update({'subtitle': '',})
return render_to_response('listpage.html', pagedata)
def singlepost(request, year, month, slug2):
posts, pagedata = init()
post = posts.get(date_created__year=year,
date_created__month=int(month),
slug=slug2,)
pagedata.update({'post': post})
return render_to_response('singlepost.html', pagedata)
def yearview(request, year):
posts, pagedata = init()
posts = posts.filter(date_created__year=year)
pagedata.update({'post_list': posts,
'subtitle': 'Posts for %s' % year})
return render_to_response('listpage.html', pagedata)
def monthview(request, year, month):
posts, pagedata = init()
posts = posts.filter(date_created__year=year)
posts = posts.filter(date_created__month=int(month))
pagedata.update({'post_list': posts,
'subtitle': 'Posts for %s %s' % (MONTH_NAMES[int(month)], year),})
return render_to_response('listpage.html', pagedata)
def tagview(request, tag):
allposts, pagedata = init()
posts = []
for post in allposts:
tags = re.split(' ', post.tags)
if tag in tags:
posts.append(post)
pagedata.update({'post_list': posts,
'subtitle': "Posts tagged '%s'" % tag,})
return render_to_response('listpage.html', pagedata)
def init():
posts = Post.objects.all()
tag_data = create_tag_data(posts)
archive_data = create_archive_data(posts)
pagedata = {'version': '0.0.5',
'post_list': posts,
'tag_counts': tag_data,
'archive_counts': archive_data,}
return posts, pagedata
def create_archive_data(posts):
archive_data = []
count = {}
mcount = {}
for post in posts:
year = post.date_created.year
month = post.date_created.month
if year not in count:
count[year] = 1
mcount[year] = {}
else:
count[year] += 1
if month not in mcount[year]:
mcount[year][month] = 1
else:
mcount[year][month] += 1
for year in sorted(count.iterkeys(), reverse=True):
archive_data.append({'isyear': True,
'year': year,
'count': count[year],})
for month in sorted(mcount[year].iterkeys(), reverse=True):
archive_data.append({'isyear': False,
'yearmonth': '%d/%02d' % (year, month),
'monthname': MONTH_NAMES[month],
'count': mcount[year][month],})
return archive_data
def create_tag_data(posts):
tag_data = []
count = {}
for post in posts:
tags = re.split(" ", post.tags)
for tag in tags:
if tag not in count:
count[tag] = 1
else:
count[tag] += 1
for tag, count in sorted(count.iteritems(), key=lambda(k, v): (v, k), reverse=True):
tag_data.append({'tag': tag,
'count': count,})
return tag_data</pre>
<br /><br /><strong>Templates</strong><br />
I made the following template changes:
<ul>
<li>I modified the sidebar in <code>base.html</code> to display an archive index
and a tag index.</li>
<li>I replaced <code>frontpage.html</code> with a more generic
<code>listpage.html</code> template used for displaying the frontpage, archives,
and tag views.</li>
<li>I added a <code>singlepost.html</code> template for displaying a single
post view.</li>
<li>In all the templates, I created links to navigate among the different
views.</li>
</ul>
<code>~/src/django/myblogsite/templates/base.html</code>:
<pre style="height: 200px; overflow: auto;"><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.5.2/build/reset-fonts-grids/reset-fonts-grids.css">
<link rel="stylesheet" type="text/css" href="/site_media/css/mystyle.css">
<title>{% block title %}Sofeng's Blog {{ version }}{% endblock %}</title>
</head>
<body>
<div id="doc" class="yui-t4">
<div id="hd">
<h1>
<img align="center" src="/site_media/image/crane.jpg">
{% block header1 %}Sofeng's Blog {{ version }}{% endblock %}
</h1>
<h2>{% block header2 %}{% endblock %}</h2>
</div>
<div id="bd">
<div id="yui-main">
<div class="yui-b">
{% block main %}{% endblock %}
</div>
</div>
<div class="yui-b" id="sidebar">
{% block sidebar %}
<h4>ABOUT</h4>
<p>This is my new blog created using <a href="http://www.djangoproject.com">Django</a>,
a <a href="http://www.python.org">Python</a> web framework. This site is under
construction. My current blog is located at:
<a href="http://iwiwdsmi.blogspot.com">http://iwiwdsmi.blogspot.com</a>.
</p>
<br>
<h4>TAGS</h4>
{% for line in tag_counts %}
<a href="/blog/tag/{{ line.tag }}/">{{ line.tag }}</a> ({{ line.count }})<br>
{% endfor %}
<br>
<h4>ARCHIVE</h4>
{% for line in archive_counts %}
{% if line.isyear %}
{% if not forloop.first %}
<br>
{% endif %}
<a href="/blog/{{ line.year }}/">{{ line.year }}</a> ({{ line.count }})<br>
{% else %}
<a href="/blog/{{ line.yearmonth }}/">{{ line.monthname }}</a> ({{ line.count }})<br>
{% endif %}
{% endfor %}
<br>
{% endblock %}
</div>
</div>
<div id="ft">
Created with <a href="http://www.djangoproject.com/">Django</a>.<br>
Hosted by <a href="http://www.webfaction.com/">Webfaction</a>.
</div>
</div>
</body>
</html></pre>
<code>~/src/django/myblogsite/templates/listpage.html</code>:
<pre style="height: 200px; overflow: auto;">{% extends "base.html" %}
{% block title %}
Sofeng's Blog {{ version }}
{% if subtitle %}:{% endif %}
{{ subtitle }}
{% endblock %}
{% block header1 %}
{% if subtitle %}
<a href="/blog/">Sofeng's Blog {{ version }}</a>
{% else %}
Sofeng's Blog {{ version }}
{% endif %}
{% endblock %}
{% block header2 %}
{{ subtitle }}
{% endblock %}
{% block main %}
{% for post in post_list %}
<h3><a href="/blog/{{ post.date_created|date:"Y/m" }}/{{ post.slug }}/">
{{ post.title }}</a>
</h3>
{{ post.body }}
<hr>
<div class="post_footer">
{% ifnotequal post.date_modified.date post.date_created.date %}
Last modified: {{ post.date_modified.date }}<br>
{% endifnotequal %}
Date created: {{ post.date_created.date }}<br>
Tags:
{% for tag in post.get_tag_list %}
<a href="/blog/tag/{{ tag }}/">{{ tag }}</a>{% if not forloop.last %}, {% endif %}
{% endfor %}
</div>
<br>
{% endfor %}
{% endblock %}</pre>
<code>~/src/django/myblogsite/templates/singlepost.html</code>:
<pre style="height: 200px; overflow: auto;">{% extends "base.html" %}
{% block title %}
Sofeng's Blog {{ version }}: {{ post.title }}
{% endblock %}
{% block header1 %}
<a href="/blog/">Sofeng's Blog {{ version }}</a>
{% endblock %}
{# to fix IE #}
{% block header2 %} {% endblock %}
{% block main %}
<h3>{{ post.title }}</h3>
{{ post.body }}
<hr>
<div class="post_footer">
{% ifnotequal post.date_modified.date post.date_created.date %}
Last modified: {{ post.date_modified.date }}<br>
{% endifnotequal %}
Date created: {{ post.date_created.date }}<br>
Tags:
{% for tag in post.get_tag_list %}
<a href="/blog/tag/{{ tag }}/">{{ tag }}</a>{% if not forloop.last %}, {% endif %}
{% endfor %}
</div>
<br>
{% endblock %}</pre>
<br /><br /><strong><code>urls.py</code></strong><br />
<p>As the last step in my bottom-up approach, I modified my <code>urls.py</code>
and <code>urls_webfaction.py</code>.</p>
<code>~/src/django/myblogsite/urls_webfaction.py</code>:
<pre>from django.conf.urls.defaults import *
from myblogsite.myblogapp.views import *
urlpatterns = patterns(
'',
(r'^admin/', include('django.contrib.admin.urls')),
(r'^myview1/$', myview1),
(r'^blog/$', frontpage),
(r'^blog/(\d{4,4})/(\d{2,2})/([\w\-]+)/$', singlepost),
(r'^blog/(\d{4,4})/$', yearview),
(r'^blog/(\d{4,4})/(\d{2,2})/$', monthview),
(r'^blog/tag/([\w\-]+)/$', tagview),
)</pre>
<br /><br /><strong>Finish</strong><br />
<p>I uploaded, updated, and restarted the Apache server as usual.</p>
<br />Here is a snapshot screenshot of the site:<br />
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_lZhqNsiakm4/SGXmzRHzpNI/AAAAAAAAAFQ/AVKA5d4I69A/s1600-h/version0.0.5.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://bp3.blogger.com/_lZhqNsiakm4/SGXmzRHzpNI/AAAAAAAAAFQ/AVKA5d4I69A/s320/version0.0.5.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5216829511822451922" /></a>
<br />The live site is located at:
<a href="http://saltycrane.com/blog/">http://saltycrane.com/blog/</a><br />
<br />Related posts:<br />
<a href="http://www.saltycrane.com/blog/2008/05/django-new-blog-project/">
Django Blog Project #1: Creating a basic blog</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/first-django-webfaction-deployment/">
Django Blog Project #2: Deploying at Webfaction</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-3-using-css-and/">
Django Blog Project #3: Using CSS and Template Inheritance</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-4-adding-post/">
Django Blog Project #4: Adding post metadata</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-5-yui-css-and/">
Django Blog Project #5: YUI CSS and serving static media</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-7-adding-simple/">
Django Blog Project #7: Adding a simple Atom feed</a><br />
<a href="http://www.saltycrane.com/blog/2008/07/django-blog-project8-adding-basic/">
Django Blog Project #8: Adding basic comment functionality</a><br />
<br />
Django Blog Project #5: YUI CSS and serving static media
2008-06-16T00:04:00-07:00https://www.saltycrane.com/blog/2008/06/django-blog-project-5-yui-css-and/<p>I wrote about adding some CSS to my new blog in my
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-3-using-css-and/">
post #3</a>. However, I got the CSS layout code from a book that
was a couple of years old (<em>CSS Cookbook, 2006</em>). I created a 2 column fixed
width layout, but it was not centered. It turns out creating a robust, cross-browser
solution to centering a page layout is not trivial, even in 2008.</p>
<p>I happened to notice
that the <a href="http://www.djangobook.com/">Django Book</a> used YUI style sheets
for styling its online book. The
<a href="http://developer.yahoo.com/yui/">Yahoo User Interface Library</a> (YUI) consists
mostly of a Javascript Library, but it also includes "several core CSS resources".
Doing a search among Django blogs using
<a href="http://www.google.com/coop/cse?cx=012993260288651340677%3A3fwy29agfd4">
django blog search</a> revealed that YUI was a pretty popular choice for CSS among
Django users. Watching the 42 minute
<a href="http://video.yahoo.com/watch/1373808">YUI CSS introductory video</a> gave me
a better understanding of the current state of CSS and browser compatability and how
to use YUI to easily create consistent layouts across a variety of browsers.</p>
<p>So in this post I'll touch on how I used the YUI CSS library to restyle my
slowly-getting-to-be-a-fledgling site. I'll also note how I setup Django and Webfaction
to serve the static CSS media files. (What, I need a special setup to use external
CSS stylesheets? you ask. I know, it suprised me too.) Finally, I made some small
updates to my model to use more reasonable model attribute field types.</p>
<br /><b>Setup YUI-based template</b><br />
<p>In my <code>base.html</code> template file, I removed
all the CSS in between the <code><style></style></code> tags and replaced
it with a link to the YUI concatenated, minified reset+fonts+grids aggregate file. I
also added a link to my to-be-described-later local CSS stylesheet, <code>mystyle.css</code>.
In the body section, I added a number of <code>div</code> blocks to create the required
structure for the YUI library. The following code creates a fixed 750 pixel total width
layout with a 180 pixel side column on the right and a full width header and footer.
Not coming from a web background, I
had thought this type of thing would be easier. I guess this complexity contributes to the demand
for tools such as <a href="http://www.adobe.com/products/dreamweaver/">Dreamweaver</a>.
</p>
<code>~/src/django/myblogsite/templates/base.html</code>:
<pre><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.5.2/build/reset-fonts-grids/reset-fonts-grids.css">
<link rel="stylesheet" type="text/css" href="/site_media/css/mystyle.css">
<title>{% block title %}Sofeng's Blog Version {{ VERSION }}{% endblock %}</title>
</head>
<body>
<div id="doc" class="yui-t4">
<div id="hd">
<h1>Sofeng's Blog Version {{ VERSION }}</h1>
</div>
<div id="bd">
<div id="yui-main">
<div class="yui-b">
{% block main %}{% endblock %}
</div>
</div>
<div class="yui-b" id="sidebar">
{% block sidebar %}
<h3>ABOUT</h3>
<p>This is my new blog created using <a href="http://www.djangoproject.com">Django</a>,
a <a href="http://www.python.org">Python</a> web framework. This site is under
construction. My current blog is located at:
<a href="http://iwiwdsmi.blogspot.com">http://iwiwdsmi.blogspot.com</a>.
</p>
{% endblock %}
</div>
</div>
<div id="ft">
A <a href="http://www.djangoproject.com/">Django</a> site.
</div>
</div>
</body>
</html></pre>
<br /><b>Create local mystyle.css stylesheet</b><br />
<p><code>mystyle.css</code> contains the CSS code to customize the style (fonts, colors,
spacing, etc.) of my site.
Currently it is just a copy of YUI's Base library with a few minor modifications. (YUI's
Reset library removes all browser specific styling (located in <code>browser.css</code>
or similar). Then the YUI Base library adds back the common element styles that we are
familiar with. This provides consistency across browsers.) I saved this file at
<code>~/src/django/myblogsite/media/css/mystyle.css</code>.
</p>
<br /><b>Serve static CSS media files (development server)</b><br />
<p>Django doesn't serve static media files such as CSS, JavaScript, and images by default.
The reason is that Django is meant to create dynamically generated webpages and serving
static media is best left to a tool designed for this task such as Apache. However,
during development,
it is convenient to have the Django development server (<code>python manage.py
runserver</code>) serve the static media files. To do this, I used
<a href="http://www.djangoproject.com/documentation/0.96/static_files/">these instructions
from Django</a>.
This amounted to adding a new line to my URLConf (<code>urls.py</code>).
</p>
<code>~/src/django/myblogsite/urls.py</code>:
<pre>from django.conf.urls.defaults import *
from myblogsite.myblogapp.views import *
urlpatterns = patterns(
'',
(r'^admin/', include('django.contrib.admin.urls')),
(r'^myview1/$', myview1),
(r'^blog/$', frontpage),
(r'^site_media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': '/home/sofeng/src/django/myblogsite/media'}),
)</path></pre>
<br /><b>Serve static CSS media files (Webfaction server)</b><br />
<p>To serve my static CSS file at Webfaction, I followed
<a href="https://help.webfaction.com/index.php?_m=knowledgebase&_a=viewarticle&kbarticleid=98&nav=0,24">
these Webfaction knowledgebase instructions</a>:</p>
<ol>
<li>I went to the <a href="https://panel.webfaction.com/">Webfaction Control Panel</a></li>
<li>Under the ">Domains/websites" menu, I selected "Applications"</li>
<li>I selected the icon with the plus to add a new App.</li>
<li>I filled in the following fields:<br />
"Name": myblogsite_media
"App type": Symbolic link
"Extra info": /home/sofeng/webapps/django/myblogsite/media
</li>
<li>Under the ">Domains/websites" menu, I selected "Websites"</li>
<li>I clicked on the pencil icon to edit my website</li>
<li>Under "Site apps", I selected the icon with the plus to add a new site app.</li>
<li>I selected "myblogsite_media" as the App and entered <code>/site_media</code> as the
"URL path".</li>
</ol>
<p>In my <code>settings_webfaction.py</code> file I updated the <code>MEDIA_ROOT</code> and
<code>MEDIA_URL</code> variables. (Did I forget to mention I created a separate settings
file for Webfaction. I point to the <code>settings_webfaction.py</code> file in my
<code>~/webapps/django/apache2/conf/httpd.conf</code>.) I also created a new file,
<code>urls_webfaction.py</code> that doesn't have the above modification and I point to
this URLConf in my <code>settings_webfaction.py</code>. I know there has got to be a
better way to do this, but my tired brain hasn't figured it out yet.
</p>
In <code>~/src/django/myblogsite/settings_webfaction.py</code>, I modified the following lines:
<pre>MEDIA_ROOT = '/home/sofeng/webapps/django/myblogsite/media/'
MEDIA_URL = '/site_media/'
ROOT_URLCONF = 'myblogsite.urls_webfaction'</pre>
<br /><b>Upload project and deploy</b><br />
On my local machine:
<pre>$ pushwebf</pre>
On Webfaction machine:
<pre>$ hg update -C
$ ~/webapps/django/apache2/bin/restart</pre>
<br />Here is a snapshot screenshot of the site:<br />
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_lZhqNsiakm4/SFYQ6G9AGiI/AAAAAAAAAFI/vqAZqgeVQ08/s1600-h/version0.0.4.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://bp0.blogger.com/_lZhqNsiakm4/SFYQ6G9AGiI/AAAAAAAAAFI/vqAZqgeVQ08/s320/version0.0.4.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5212372209212856866" /></a>
The live site can be viewed at: <a href="http://saltycrane.com/blog/">http://saltycrane.com/blog/</a><br />
<br />Related posts:<br />
<a href="http://www.saltycrane.com/blog/2008/05/django-new-blog-project/">
Django Blog Project #1: Creating a basic blog</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/first-django-webfaction-deployment/">
Django Blog Project #2: Deploying at Webfaction</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-3-using-css-and/">
Django Blog Project #3: Using CSS and Template Inheritance</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-4-adding-post/">
Django Blog Project #4: Adding post metadata</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-6-creating-standard/">
Django Blog Project #6: Creating standard blog views</a><br />
<br />
Django Blog Project #4: Adding post metadata
2008-06-10T23:24:00-07:00https://www.saltycrane.com/blog/2008/06/django-blog-project-4-adding-post/<p>I've created a basic blog using SQLite as a backend, deployed it at a
shared hosting provider, Webfaction, and added a little CSS for style.
Now what? Well, while I say I've created a basic blog, I should say
it's a not-quite-yet-basic blog. All it has is some text, which I call
Posts, separated by <code><hr></code> tags. The next step is to add some
post metadata, such as a title, date of creation and tags.</p>
<br /><b>Modify the <code>Post</code> model</b><br />
<p>To create this new metadata, I first modified the <code>Post</code>
model in <code>~/src/django/myblogsite/myblogapp/models.py</code>:</p>
<pre>from django.db import models
class Post(models.Model):
title = models.CharField(maxlength=500)
date_created = models.DateField()
tags = models.CharField(maxlength=200)
body = models.TextField()
def __str__(self):
return self.title
class Meta:
ordering = ["-id"]
class Admin:
pass</pre>
<p>I added new attributes: <code>title</code>, <code>date_created</code>,
and <code>tags</code>. The <code>__str__</code> method is used
to identify a <code>Post</code> instance by its title. This is
useful when working in the Administration page. The <code>class Meta:</code>
is used to reverse sort the Posts by "id".</p>
<p><em>Correction 7/6/2008</em>: For the Post's body field, I previously
used the line: <code>body = models.CharField(maxlength=999999)</code>.
However, thanks to <a href="http://mylesbraithwaite.com/">Myles</a>'s
comment below, I've changed this to use the more appropriate <code>TextField</code>.</p>
<br /><b style="font-size:130%"><code>python manage.py syncdb</code></b><br />
<p>Whoops, running <code>python manage.py syncdb</code> doesn't actually
sync the model with the database. It is only used to initially create
the database tables. To add the new columns to my table, I would have
to enter the SQL commands directly. Though it is not super difficult,
and would be good experience since all those hiring managers seem to
want SQL experience, I took the lazy route and just replaced my database.
(Not like I had <a href="http://bp1.blogger.com/_lZhqNsiakm4/SE4dA5B21QI/AAAAAAAAAE4/Ek3yuLldw0Q/s1600-h/version0.0.2.png">
much in there</a> anyways.) For your reference,
the section <em>Making Changes to a Database Schema</em>
in <a href="http://www.djangobook.com/en/1.0/chapter05/">Chapter 5 of
The Django Book</a> describes how to solve my problem without
replacing my database.</p>
<pre>$ cd ~/src/django/myblogsite
$ rm mydatabase.sqlite3
$ 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
Creating table myblogapp_post
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 sofeng'): sofeng
E-mail address: my@email.address
Password:
Password (again):
Superuser created successfully.
Installing index for auth.Message model
Installing index for auth.Permission model
Installing index for admin.LogEntry model
Loading 'initial_data' fixtures...
No fixtures found.</pre>
<p>Oh yeah, I forgot gotta recreate my superuser.</p>
<br /><b>Modify <code>views.py</code> slightly</b><br />
<p>In versions 0.0.1 and 0.0.2 of my new blog, I passed a list
of post bodies to my template. But actually, I could have just
passed a list of the <code>Post</code> objects and accessed
the body attribute of each post in the template. This saves
a line of unecessary code, and provides accessiblity to
all the attributes of the <code>Post</code> object. (I also
factored out my blog version number from the <code>base.html</code>
template.)</p>
<code>~/src/django/myblogsite/myblogapp/views.py</code>:
<pre>from django.shortcuts import render_to_response
from myblogsite.myblogapp.models import Post
def frontpage(request):
posts = Post.objects.all()
return render_to_response('frontpage.html',
{'post_list': posts,
'VERSION': '0.0.3'})</pre>
<em>Correction 7/6/2008</em>: I previously had <code>from myblogapp.models
import Post</code> on the second line. This works, but is inconsistent
with my <code>urls.py</code> below and can (and did for me) cause
subtle errors in the future. I corrected the line to read:
<code>from myblogsite.myblogapp.models import Post</code>.<br />
<br /><b>Modify my <code>frontpage.html</code> template</b><br />
<p>This is pretty self-explanatory. I access the the <code>Post</code>
object's attributes using the "<code>.</code>" (dot).</p>
<code>~/src/django/myblogsite/templates/frontpage.html</code>:
<pre>{% extends "base.html" %}
{% block main %}
{% for post in post_list %}
<h2>{{ post.title }}</h2>
{{ post.body }}
<hr>
<div class="post_footer">Created: {{ post.date_created }} | Tags: {{ post.tags }}</div>
{% endfor %}
{% endblock %}</pre>
<br /><b>A little CSS</b><br />
<p>In between the <code><style></code> tags in <code>
~/src/django/myblogsite/templates/base.html</code>:
<pre>.post_footer {
font-family: Verdana, Arial, sans-serif;
font-size:70%;
}
hr {
border: 0;
color: gray;
background-color: gray;
height: 1px;
}</pre>
<br /><b>Start development server and add a couple posts</b><br />
</p><p>I started the development server and added a couple of posts in the Admin site:</p>
<ul>
<li>
<pre>$ cd ~/src/django/myblogsite
$ python manage.py runserver</pre>
</li>
<li>I visited <code>http://127.0.0.1:8000/admin</code>, logged in, and added a couple posts.
</li>
</ul>
<br /><b>Upload to Webfaction server</b><br />
<p><code>pushwebf</code> is my alias for
<code>hg push --remotecmd /home/sofeng/bin/hg ssh://sofeng@sofeng.webfactional.com/webapps/django/myblogsite</code>
</p>
<pre>$ pushwebf
sofeng@sofeng.webfactional.com's password:
pushing to ssh://sofeng@sofeng.webfactional.com/webapps/django/myblogsite
searching for changes
remote: adding changesets
remote: adding manifests
remote: adding file changes
remote: added 7 changesets with 20 changes to 10 files</pre>
<br /><b>Deploy</b><br />
Logged into Webfaction:
<pre>$ cd ~/webapps/django/myblogsite
$ hg update -C
9 files updated, 0 files merged, 4 files removed, 0 files unresolved
$ ~/webapps/django/apache2/bin/restart</pre>
<p>Here is a snapshot screenshot of version 0.0.3</p>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_lZhqNsiakm4/SE9wUjJ4HDI/AAAAAAAAAFA/T6eDSpQQPGk/s1600-h/version0.0.3.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://bp3.blogger.com/_lZhqNsiakm4/SE9wUjJ4HDI/AAAAAAAAAFA/T6eDSpQQPGk/s320/version0.0.3.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5210506792227314738" /></a>
<p>The live site can be viewed at <a href="http://saltycrane.com/blog/">http://saltycrane.com/blog/</a></p><br />
<br />Related posts:<br />
<a href="http://www.saltycrane.com/blog/2008/05/django-new-blog-project/">
Django Blog Project #1: Creating a basic blog</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/first-django-webfaction-deployment/">
Django Blog Project #2: Deploying at Webfaction</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-3-using-css-and/">
Django Blog Project #3: Using CSS and Template Inheritance</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-5-yui-css-and/">
Django Blog Project #5: YUI CSS and serving static media</a><br />
<br />
Django Blog project #3: Using CSS and Template Inheritance
2008-06-07T17:50:00-07:00https://www.saltycrane.com/blog/2008/06/django-blog-project-3-using-css-and/<p>Version 0.0.1 of my new blog had no style. For 0.0.2, I added a little
CSS to my Django template to make it look a little better.</p>
<b>Create new Django templates</b><br />
<p>I created two new Django templates, <code>base.html</code> and
<code>frontpage.html</code>. <code>base.html</code> contains
all the HTML and CSS that is common to all the web pages that
will be on this site. <code>frontpage.html</code>
is the template for the front page of my new blog. It uses the
object-oriented technique of <em>extending</em> my <code>base.html</code>
template. It uses the boilerplate code in <code>base.html</code>
and then fills in sections with content specific to the front page.
It is similar to server side includes, but more powerful because
specific content can be inserted anywhere instead of just at the
beginning or end. Django does have an
<em>include</em> mechanism which acts like server side includes,
but the <em>extension</em> method
(also called <em>Template Inheritance</em>) is the preferred way
of doing things.</p>
<br />Here is the code for <code>frontpage.html</code>:<br />
<pre>{% extends "base.html" %}
{% block main %}
{% for post in post_list %}
{{ post }}
<hr />
{% endfor %}
{% endblock %}</pre>
Here is the code for <code>base.html</code>:
<pre><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style>
body {
margin: 0;
padding: 0;
font-family: Georgia, Times, "Times New Roman", serif;
color: black;
width: 600px;
border-right: 1px solid black;
}
#header {
background-color: #666;
border-bottom: 1px solid #333;
}
#sidebarLeft {
float: left;
width: 160px;
margin-left: 10px;
padding-top: 1em;
}
#mainRight {
padding-top: 1em;
margin: 0 2em 0 200px;
}
#footer {
clear: both;
background-color: #ccc;
padding-bottom: 1em;
border-top: 1px solid #333;
padding-left: 200px;
}
h2.sidebar {
font-size: 120%;
}
</style>
<title>{% block title %}Sofeng's Blog Version 0.0.2{% endblock %}</title>
</head>
<body>
<div id="header">
<h1>Sofeng's Blog Version 0.0.2</h1>
</div>
<div id="sidebarLeft">
{% block sidebar %}
<h2 class="sidebar">ABOUT</h2>
<p>This is my new blog created using <a href="http://www.djangoproject.com">Django</a>,
a <a href="http://www.python.org">Python</a> web framework. This site is under
construction. My current blog is located at:
<a href="http://iwiwdsmi.blogspot.com">http://iwiwdsmi.blogspot.com</a>.
</p>
{% endblock %}
</div>
<div id="mainRight">
{% block main %}{% endblock %}
</div>
<div id="footer">
A <a href="http://www.djangoproject.com/">Django</a> site.
</div>
</body>
</html></pre>
<br /><b>Modify view to use new template</b><br />
<p>To use the new template, I created a new view called <code>frontpage</code>
in <code>~/src/django/myblogsite/myblogapp/views.py</code>:</p>
<pre>from django.shortcuts import render_to_response
from myblogsite.myblogapp.models import Post
def frontpage(request):
posts = Post.objects.all()
post_body_list = [post.body for post in posts]
return render_to_response('frontpage.html',
{'post_list': post_body_list})</pre>
<em>Correction 7/6/2008</em>: I previously had <code>from myblogapp.models
import Post</code> on the second line. This works, but is inconsistent
with my <code>urls.py</code> below and can (and did for me) cause
subtle errors in the future. I corrected the line to read:
<code>from myblogsite.myblogapp.models import Post</code>.<br />
<br /><b>Map an URL to the new view</b><br />
<p>To complete the change, I mapped the url <code>/blog</code> to the new
<code>frontpage</code> view in <code>~/src/django/myblogsite/urls.py</code>:
<pre>from django.conf.urls.defaults import *
from myblogsite.myblogapp.views import *
urlpatterns = patterns(
'',
(r'^admin/', include('django.contrib.admin.urls')),
(r'^myview1/$', myview1),
(r'^blog/$', frontpage),
)</pre>
<br /><b>Upload project to Webfaction server</b><br />
</p><p>I updated my <a href="http://www.saltycrane.com/blog/2008/06/first-django-webfaction-deployment/">
previous post</a> to include a section on installing Mercurial.
Here are my steps for pushing my changes to the server. It's a little more
complicated than it should be because I was mucking with the repository,
but future pushes should be much easier.</p>
<pre>$ hg pull -u --remotecmd /home/sofeng/bin/hg ssh://sofeng@sofeng.webfactional.com/webapps/django/myblogsite
sofeng@sofeng.webfactional.com's password:
pulling from ssh://sofeng@sofeng.webfactional.com/webapps/django/myblogsite
searching for changes
adding changesets
adding manifests
adding file changes
added 5 changesets with 4 changes to 5 files (+1 heads)
not updating, since new heads added
(run 'hg heads' to see heads, 'hg merge' to merge)
$ hg merge
local changed templates/base.html which remote deleted
use (c)hanged version or (d)elete? c
3 files updated, 0 files merged, 3 files removed, 0 files unresolved
(branch merge, don't forget to commit)
$ hg commit -m 'commit after merge'
$ hg push --remotecmd /home/sofeng/bin/hg ssh://sofeng@sofeng.webfactional.com/webapps/django/myblogsite
sofeng@sofeng.webfactional.com's password:
pushing to ssh://sofeng@sofeng.webfactional.com/webapps/django/myblogsite
searching for changes
remote: adding changesets
remote: adding manifests
remote: adding file changes
remote: added 3 changesets with 5 changes to 6 files</pre>
<br /><b>Deploy</b><br />
<p>With my cleanup of the mercurial repository, I didn't have to make any
file changes. I just updated the working directory of my project using
<code>hg update -C</code> and restarted the Apache web server.</p>
<pre>$ cd ~/webapps/django/myblogsite
$ hg update -C
5 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ ~/webapps/django/apache2/bin/restart</pre>
<p>Now, pointing my browser at <a href="http://saltycrane.com/blog/">
http://saltycrane.com/blog/</a>, shows new version 0.0.2 of my blog!</p>
<p>Here is a snapshot screenshot of my blog version 0.0.2:</p>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_lZhqNsiakm4/SE4dA5B21QI/AAAAAAAAAE4/Ek3yuLldw0Q/s1600-h/version0.0.2.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://bp1.blogger.com/_lZhqNsiakm4/SE4dA5B21QI/AAAAAAAAAE4/Ek3yuLldw0Q/s320/version0.0.2.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5210133720060056834" /></a>
<br />Related posts:<br />
<a href="http://www.saltycrane.com/blog/2008/05/django-new-blog-project/">
Django Blog Project #1: Creating a basic blog</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/first-django-webfaction-deployment/">
Django Blog Project #2: Deploying at Webfaction</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-4-adding-post/">
Django Blog Project #4: Adding post metadata</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-5-yui-css-and/">
Django Blog Project #5: YUI CSS and serving static media</a><br />
<br />
Django Blog Project #2: Django Webfaction deployment
2008-06-06T11:12:00-07:00https://www.saltycrane.com/blog/2008/06/first-django-webfaction-deployment/<p>I mentioned in my <a href="http://www.saltycrane.com/blog/2008/05/django-new-blog-project/">
previous Django post</a> that I'd write about hosting my new Django blog site on Webfaction.
As promised, here it is.</p>
<p>I had some problems deploying my first site at Webfaction. First I got an error about pysqlite.
I tried using Postgresql, and then I got an error about something else when trying to do a <code>
python2.5 manage.py syncdb</code>. I think that they didn't set me up with a current
Django installation. Luckily the support was prompt and helpful. I ended up reinstalling
the Django application from the Webfaction Control Panel. Here are the steps I took
to deploy my less than fledgling site.</p>
<br /><b>Sign up for a shared hosting plan at Webfaction</b><br />
<p>I signed up for Webfaction's lowest plan at $9.50/month with no commitment.
<a href="http://www.webfaction.com/services/hosting">Here is their pricing plans.</a>
</p>
<br /><b>Play with new account</b><br />
<p>I got a welcome email within 24 hours with the details about my account.
<code>sofeng.webfactional.com</code> was a domain name which they provided. Visiting this
address in my browser showed my the <em>It worked!</em> Django new project page. I also ssh'd into
my shell account by using <code>ssh sofeng.webfactional.com</code>. The OS is
CentOS5 which, I think, is based on Red Hat. It was a little different than the
Ubuntu that I'm used to. I copied some of my configuration files over to make
me feel more at home. On my home machine:
<pre>$ scp .bashrc sofeng@sofeng.webfactional.com:
$ scp .screenrc sofeng@sofeng.webfactional.com:
$ scp .emacs sofeng@sofeng.webfactional.com:</pre>
</p>
<br /><b>Reinstall Django</b><br />
<p>Note, if your Webfaction account is set up properly with Django, you shouldn't
need to take these steps. Like I mentioned earlier, I had some problems with my
Django installation. So
I ended up reinstalling Django. It's not as hard as it sounds-- just a few clicks
in the Control Panel.</p>
<ol>
<li>In my browser, I went to the Webfaction Control Panel at
<a href="https://panel.webfaction.com/">https://panel.webfaction.com/</a>.
</li>
<li>Under "> Domains / websites", I selected "Applications".</li>
<li>I removed my current Django installation by clicking the icon with the minus sign.</li>
<li>I clicked the icon with the plus sign to add a new application.</li>
<li>I filled in the following information:
<blockquote>"Name:": django<br />
"App type:": Django 0.96.2/mod_python 3.3.1/Python 2.5</blockquote>
Then I clicked the "Create" button.
</li>
<li>Finally, I needed to specify which URL path I want to use with my Django application.<br />
Under "> Domains / websites", I selected "Websites".
</li>
<li>I selected the icon with the pencil to edit the website settings.</li>
<li>In the "Site apps:" section, I selected the icon with the plus sign to add a new App and path.
I selected "django" as my "App:" and entered "/" (without the quotes) in the "URL path" field.
Then I clicked "Update".
</li>
<li>After a couple minutes, I was able to view the Django <em>It worked!</em> page at
<code>http://sofeng.webfactional.com</code>.
</li>
</ol>
<br /><b>Upload my project</b><br />
<p>
<ol>
<li>The welcome message recommends using <code>sftp</code>, but I used <code>rsync</code> instead because I'm
more familiar with it:
<pre>sofeng@home:~ $ rsync -avz ~/src/django/myblogsite sofeng@sofeng.webfactional.com:webapps/django</pre>
</li>
</ol>
<br /><b>Deploy</b><br />
<ol>
<li>I ssh'd into my webfaction shell account:
<pre>sofeng@home:~ $ ssh sofeng@sofeng.webfactional.com</pre>
</li>
<li>I set the PYTHONPATH. (I actually put this in my .bash_profile)
<pre>sofeng@web36:~ $ export PYTHONPATH=$HOME/webapps/django</pre>
</li>
<li><pre>sofeng@web36:~ $ cd ~/webapps/django/myblogsite</pre></li>
<li>I tried to do a <code>python2.5 manage.py syncdb</code>, but got a database error:
<pre style="height: 40; overflow:auto">Traceback (most recent call last):
File "manage.py", line 11, in <module>
execute_manager(settings)
File "/home/sofeng/webapps/django/lib/python2.5/django/core/management.py", line 1672, in execute_manager
execute_from_command_line(action_mapping, argv)
File "/home/sofeng/webapps/django/lib/python2.5/django/core/management.py", line 1571, in execute_from_command_line
action_mapping[action](int(options.verbosity), options.interactive)
File "/home/sofeng/webapps/django/lib/python2.5/django/core/management.py", line 504, in syncdb
cursor = connection.cursor()
File "/home/sofeng/webapps/django/lib/python2.5/django/db/backends/sqlite3/base.py", line 58, in cursor
self.connection = Database.connect(**kwargs)
sqlite3.OperationalError: unable to open database file</module></pre>
</li>
<li>I needed to edit my <code>settings.py</code> file. I changed the following lines:
<pre>DATABASE_NAME = '/home/sofeng/webapps/django/myblogsite/mydatabase.sqlite3'
TEMPLATE_DIRS = (
'/home/sofeng/webapps/django/myblogsite/templates',
)</pre>
</li>
<li>I tried <code>python2.5 manage.py syncdb</code> again:
<pre>$ python2.5 manage.py syncdb
Loading 'initial_data' fixtures...
No fixtures found.</pre>
It worked.
</li>
<li>The next step was to set up the Apache configuration.
<pre>$ cd ~/webapps/django/apache2/conf</pre>
</li>
<li>I edited the following line in <code>httpd.conf</code>:
<pre> SetEnv DJANGO_SETTINGS_MODULE myblogsite.settings</pre>
</li>
<li>Then I restarted the Apache server:
<pre>$ ~/webapps/django/apache2/bin/restart</pre>
</li>
<li>In my browser, I went to <code>http://sofeng.webfactional.com/myview1</code>,
but got an error:
<pre>ImportError at /myview1/
No module named blog.models
Request Method: GET
Request URL: http://sofeng.webfactional.com/myview1/
Exception Type: ImportError
Exception Value: No module named blog.models
Exception Location: /home/sofeng/webapps/django/lib/python2.5/django/core/urlresolvers.py in _get_urlconf_module, line 177</pre>
</li>
<li>I guess I need my project on the Python path. I edited the following line in <code>httpd.conf</code>:
<pre> PythonPath "['/home/sofeng/webapps/django/myblogsite', '/home/sofeng/webapps/django', '/home/sofeng/webapps/django/lib/python2.5'] + sys.path"</pre>
</li>
<li>Then I went back to <code>http://sofeng.webfactional.com/myview1</code> and saw my page. Success!
</li>
</ol>
<br /><b>Install Mercurial</b><br />
</p><p>Update 6/7/2008: By default Webfaction doesn't come with Mercurial installed, but they do allow
you to do a user installation. I decided it would be useful to have Mercurial so I could
pull and merge changes with greater control. Here are my steps.This will do a user-wide
(as opposed to system-wide) installation of Mercurial in <code>~/bin</code> and <code>~/lib</code>.</p>
<ol>
<li><pre>sofeng@home:~/incoming $ wget http://www.selenic.com/mercurial/release/mercurial-1.0.1.tar.gz</pre></li>
<li><pre>sofeng@home:~/incoming $ scp mercurial-1.0.1.tar.gz sofeng@sofeng.webfactional.com:</pre></li>
<li><pre>sofeng@web36:~ $ mv mercurial-1.0.1.tar.gz ~/tmp</pre></li>
<li><pre>$ cd tmp</pre></li>
<li><pre>$ tar zxvf mercurial-1.0.1.tar.gz</pre></li>
<li><pre>$ cd mercurial-1.0.1</pre></li>
<li><pre>$ make install-home</pre></li>
<li><pre>$ export PYTHONPATH=$HOME/lib/python:$PYTHONPATH</pre>
Note: you will need to put this in your <code>.bashrc</code> instead of your
<code>.bash_profile</code> because Mercurial only executes <code>.bashrc</code>
on a remote <code>hg push</code> by default.
</li>
<li><pre>$ hg version
Mercurial Distributed SCM (version 1.0.1)
Copyright (C) 2005-2008 Matt Mackall <mpm> and others
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.</mpm></pre>
</li>
</ol>
<br /><b>Other Errors</b><br />
<ul>
<li>No module name django.core.management<br />
<pre>$ python manage.py syncdb
Traceback (most recent call last):
File "manage.py", line 2, in ?
from django.core.management import execute_manager
ImportError: No module named django.core.management</pre>
You need to set the <code>PYTHONPATH</code>:
<pre>$ export PYTHONPATH=$HOME/webapps/django</pre>
You may get this if you use <code>python manage.py syncdb</code> instead of
<code>python2.5 manage.py syncdb</code>.
</li>
</ul>
<br />Related posts:<br />
<a href="http://www.saltycrane.com/blog/2008/05/django-new-blog-project/">
Django Blog Project #1: Creating a basic blog</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-3-using-css-and/">
Django Blog Project #3: Using CSS and Template Inheritance</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-4-adding-post/">
Django Blog Project #4: Adding post metadata</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-5-yui-css-and/">
Django Blog Project #5: YUI CSS and serving static media</a><br />
<br />
Django Blog Project #1: Creating a basic blog
2008-05-09T23:41:00-07:00https://www.saltycrane.com/blog/2008/05/django-new-blog-project/<p>It's been a while since my
<a href="http://www.saltycrane.com/blog/2007/11/django-project-3-creating-models/">
last post</a> on Django. I became very busy <del>but also found the
<a href="http://www.djangoproject.com/documentation/0.96/tutorial01/">Django
tutorial</a> to be somewhat dry</del><ins>and also somewhat lazy*</ins>.
Luckily, the official
<a href="http://www.djangobook.com/">Django Book</a> was published during this
time and it is more interesting to read. This post will be a very brief summary
of the first
6 chapters of the Django Book as I apply it towards the creation of a new blog
website. I highly recommend reading the book (I forgot to mention it is available
free online). Then after reading the first six chapters, I hope this post can
serve as kind of a refresher on how to put everything together.</p>
<p>As I mentioned, I decided to create my own blog site as my first Django project.
I know it is not the most original idea in the world, but I thought it would be
useful and a good learning experience. The following steps are my first cut
at my new blog (dubbed 0.0.1) and basically just document the basic concepts
of Django without providing much actual functionality.</p>
<p>I develop a model, a
template, and a view in accordance with Django's MTV (see also
<a href="http://en.wikipedia.org/wiki/Model-view-controller">MVC</a>)
development pattern. The model
is of a blog post (aptly named <code>Post</code>) and contains only one
attribute, the post body data (i.e. the actual text of the post). I should
add in other data such as the title, date, tags, etc. But in order to keep
things simple this first time around, it just has the post body. The model
is connected to a SQLite database and is updated using Django's excellent
admin interface. Finally, the model data is combined with a very basic template
which just displays a title (<em>My New Blog Version 0.0.1</em>), and all
the blog post bodies separated by a <hr>. Like I said, it's not
very useful at this point, but I think I understand the basic concepts and how
to put everything together much better now. The next step in my Django development
will be to create some
more interesting templates and views and add more useful data like titles and
dates.</p>
<p>I also have a couple of related plans:
</p><ul>
<li>Set up hosting: I've decided to use <a href="http://www.webfaction.com/">
WebFaction</a> for my hosting but I need to set up and upload my new,
almost-website there.
This will probably be the subject of my next Django post.
</li>
<li>Copy my Blogger posts over to my new site. I've already figured out how to
use <a href="http://www.crummy.com/software/BeautifulSoup/">Beautiful Soup</a>
to screen scrape my Blogger posts and import them into SQLite. Likely I will do
this further on in the process.
</li>
</ul>
<p></p>
<p>Here are the steps I took for my first cut at my new blog. Note, I'm
running on Ubuntu so that's why I have the <code>$</code> bash prompt
and use <code>/home/sofeng</code> paths in my examples.</p>
<p>*<em>Update 2008-10-09</em>: I realize my assessment of the tutorial
might have sounded critical-- in actuality, since I was job searching at the time, I was just
trying to avoid sounding lazy. For the record, I find the Django documentation to
be excellent and one of the highlights of the project.</p>
<h5>Create a new project</h5>
<p>The first thing to do after installing Django is to create a new project.
Luckily, it takes just one command to create the project.</p>
<ol>
<li>Create a new project
<pre>$ cd ~/src/django
$ django-admin.py startproject myblogsite</pre>
</li>
<li>Take a look at the site using the development server
<pre>$ python manage.py runserver</pre>
Then go to <a href="http://127.0.0.1:8000/">http://127.0.0.1:8000</a>
</li>
</ol>
<h5Set up the Django admin interface</h5>
<p>At first I thought the admin interface was kind of boring. However,
for my blog site, I will use the admin interface to enter new blog
posts.</p>
<ol>
<li>Edit <code>myblogsite/settings.py</code> to add the admin application to the
list of installed apps:
<pre>INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.admin',
)</pre>
</li>
<li>Install database tables for the admin interface:
<pre>$ python manage.py syncdb</pre>
At this point I was asked to create a superuser to log into the
admin interface. I answered "yes" and filled in the appropriate information.
<pre style="overflow: auto; height: 60px;">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 'sofeng'): sofeng
E-mail address: sofeng@sofeng.com
Password:
Password (again):
Superuser created successfully.
Installing index for auth.Message model
Installing index for auth.Permission model
Installing index for admin.LogEntry model
Loading 'initial_data' fixtures...
No fixtures found.</pre>
</li>
<li>Edit <code>myblogsite/urls.py</code> to include the admin url.
<pre>from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^admin/', include('django.contrib.admin.urls')),
)</pre>
</li>
<li>Run the development server:
<pre>$ python manage.py runserver</pre>
Then go to <a href="http://127.0.0.1:8000/admin">http://127.0.0.1:8000/admin</a>
Log in and take a look around.
</li></ol>
<h5>Set up the SQLite3 database</h5>
<p>I chose SQLite because it is a lightweight, simple alternative to MySQL or
PostgreSQL. This makes it great for a development website.</p>
<ol>
<li>Edit the following section in the <code>myblogsite/settings.py</code>
file:
<pre>DATABASE_ENGINE = 'sqlite3'
DATABASE_NAME = '/home/sofeng/src/django/myblogsite/mydatabase.sqlite3'</pre>
The rest of the <code>DATABASE_</code> variables are not used
with SQLite.
</li>
<li>Test out the database configuration:
Run the shell:
<pre>$ python manage.py shell</pre>
Then type these commands:
<pre>>>> from django.db import connection
>>> cursor = connection.cursor()</pre>
If nothing happens, all is good.
See Table 5-2 in <a href="http://www.djangobook.com/en/1.0/chapter05/">
Chapter 5 of the Django Book</a> common database configuration error
messages.
</li>
</ol>
<h5>Create an App</h5>
<p>I think of an "app" as a piece of specific functionality of a website, whereas a
project corresponds to a particular website. There can be many apps in a project.
Also, apps can be used in more than one project. For more information about the
differences between projects and apps see <a href="http://www.djangobook.com/en/1.0/chapter05/">
Chapter 5 of the Django Book</a>.</p>
<ol>
<li>Create an app
<pre>$ cd ~/src/django/myblogsite
$ python manage.py startapp myblogapp</pre>
</li>
</ol>
<h5>Create a Model</h5>
<p>I created one model, the <code>Post</code> model. A model roughly
corresponds to a SQL table. And each attribute in that model corresponds to a table row.
I added the <code>class Admin:</code> so that my Post model would show up
in the Admin interface (where I can insert the data).</p>
<p></p>
<ol>
<li>Edit <code>myblogsite/myblogapp/models.py</code> to look like
the following:
<pre>from django.db import models
class Post(models.Model):
body = models.TextField()
# in the future I will add these other attributes
# title = models.CharField(maxlength=500)
# timestamp = models.CharField(maxlength=50)
# tags = models.CharField(maxlength=200)
class Admin:
pass</pre>
<em>Correction 7/6/2008</em>: For the Post's body field, I previously
used the line: <code>body = models.CharField(maxlength=999999)</code>.
However, thanks to <a href="http://mylesbraithwaite.com/">Myles</a>'s
comment in my <a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-4-adding-post/">post
#4</a>, I've changed this to use the more appropriate <code>TextField</code>.
</li>
</ol>
<h5>Install the Model</h5>
<p>After writing the Python model code, I needed to create the actual tables
in the SQLite database. The following steps include a couple of checks,
then I create the tables in the last step.</p>
<ol>
<li>Edit <code>myblogsite/settings.py</code> file again and add
the blog app to the list of installed apps:
<pre>INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'myblogsite.myblogapp',
)</pre>
</li>
<li>Try validating the model:
<pre>$ python manage.py validate</pre>
Which gives the following message:
<pre>0 errors found.</pre>
</li>
<li>Check the CREATE TABLE statements that Django will generate. Note,
the database won't be modified.
<pre>$ python manage.py sqlall myblogapp</pre>
Which yields the following:
<pre>BEGIN;
CREATE TABLE "myblogapp_post" (
"id" integer NOT NULL PRIMARY KEY,
"body" text NOT NULL
);
COMMIT;</pre>
<em>Correction 7/6/2008</em>: I've updated the results here to reflect
the correction I made to the model above.
</li>
<li>Now, actually create the tables in SQLite:
<pre>$ python manage.py syncdb</pre>
Which yields something like this:
<pre>Creating table blog_post
Loading 'initial_data' fixtures...
No fixtures found.</pre>
</li>
</ol>
<h5>Create some new data using the admin interface</h5>
<p>Now that I created the models and tied them to the admin interface,
I can start adding data using the admin interface.</p>
<ol>
<li>Start the development server again:
<pre>$ python manage.py runserver</pre>
Go to <a href="http://127.0.0.1:8000/admin">http://127.0.0.1:8000/admin</a> and log in.
</li>
<li>Under the "Blog" heading, click "Posts", then add some new posts using
"Add post" and the "Save" links. This will add data to the SQLite database.
</li>
</ol>
<h5>Create a template</h5>
<p>Now I will display the data I just created using a template and a view.
The template holds all the HTML code and some simple Django template code which
the view's Python code uses to customize the page.</p>
<ol>
<li>Create the file <code>myblogsite/templates/mytemplate.html</code> and
put the following inside:
<pre><html>
<head><title>Post</title></head>
<body>
<h1>My New Blog Version 0.0.1</h1>
{% for post in post_list %}
{{ post }}
<hr />
{% endfor %}
</body>
</html></pre>
</li>
<li>Edit <code>myblogsite/settings.py</code> again to instruct Django
where to find the template files.
<pre>TEMPLATE_DIRS = (
'/home/sofeng/src/django/myblogsite/templates',
)</pre>
Be sure to include the comma at the end.
</li>
</ol>
<h5>Create a view</h5>
<p>The view is where I will grab the data from my model and insert it into my
template.</p>
<ol>
<li>Create a new file <code>myblogsite/myblogapp/views.py</code> and put the following
inside:
<pre>from django.shortcuts import render_to_response
from myblogsite.myblogapp.models import Post
def myview(request):
posts = Post.objects.all()
post_body_list = [post.body for post in posts]
return render_to_response('mytemplate.html',
{'post_list': post_body_list})</pre>
<em>Correction 7/6/2008</em>: I previously had <code>from myblogapp.models
import Post</code> on the second line. This works, but is inconsistent
with my <code>urls.py</code> below and can (and did for me) cause
subtle errors in the future. I corrected the line to read:
<code>from myblogsite.myblogapp.models import Post</code>.
</li>
</ol>
<h5>Map an URL to the new view</h5>
<p>Finally, I map an URL to my newly created view.</p>
<ol>
<li>Edit <code>myblogsite/urls.py</code> so that it looks like:
<pre>from django.conf.urls.defaults import *
from myblogsite.myblogapp.views import myview
urlpatterns = patterns('',
(r'^admin/', include('django.contrib.admin.urls')),
(r'^myview/$', myview),
)</pre>
</li>
<li>Take a look at the new page:
Run the server:
<pre>$ python manage.py runserver</pre>
Then go to <code>http://127.0.0.1:8000/myview</code>
Visiting the url shows all the posts I entered through the admin interface. Nice.
Here is a snapshot screenshot of my new blog:<br /><br />
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_lZhqNsiakm4/SE4bH_WlzYI/AAAAAAAAAEo/GmhZrA-K8ms/s1600-h/version0.0.1.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://bp0.blogger.com/_lZhqNsiakm4/SE4bH_WlzYI/AAAAAAAAAEo/GmhZrA-K8ms/s320/version0.0.1.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5210131642993462658" /></a>
</li>
</ol>
<p>That's it for now. I tried to map out the basic steps for using Django's MTV
development pattern. Hopefully, in the future, I'll be able to add more useful
features to my new Django-powered blog.</p>
<br />Related posts:<br />
<a href="http://www.saltycrane.com/blog/2007/11/django-project-1-install/">
Install Django on Ubuntu</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/first-django-webfaction-deployment/">
Django Blog Project #2: Deploying at Webfaction</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-3-using-css-and/">
Django Blog Project #3: Using CSS and Template Inheritance</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-4-adding-post/">
Django Blog Project #4: Adding post metadata</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-5-yui-css-and/">
Django Blog Project #5: YUI CSS and serving static media</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-6-creating-standard/">
Django Blog Project #6: Creating standard blog views</a><br />
<a href="http://www.saltycrane.com/blog/2008/06/django-blog-project-7-adding-simple/">
Django Blog Project #7: Adding a simple Atom feed</a><br />
<a href="http://www.saltycrane.com/blog/2008/07/django-blog-project8-adding-basic/">
Django Blog Project #8: Adding basic comment functionality</a><br />
<br />
Recommended books
2008-04-17T19:13:00-07:00https://www.saltycrane.com/blog/2008/04/recommended-books/<p>I love having a subscription to <a href="http://search.safaribooksonline.com/home">
Safari Books Online</a>. Currently my company provides a free subscription,
but if I get a new job, I might consider subscribing myself. Since I get to
browse a number of books at no cost, I thought I'd note which books are my
favorites. (Note, I am not being paid by Safari Books Online.)</p>
<h4 id="general-software">General Software</h4>
<ul>
<li><em>Structure and Interpretation of Computer Programs, Second Edition</em>,
Harold Abelson and Gerald Jay Sussman, MIT Press, ?year?<br />
I learned about this book through a job posting. It might bring you to
tears if you get it. I'm only in the second chapter. It is used in an introductory
Computer Science course at MIT. It uses Scheme (Lisp) to demonstrate concepts.<br />
Available free online at:
<a href="http://mitpress.mit.edu/sicp/full-text/book/book.html">
http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-4.html</a><br />
A <a href="http://swiss.csail.mit.edu/classes/6.001/abelson-sussman-lectures/">
free video lecture series</a> is also available.
</li>
</ul>
<h4 id="c">C</h4>
<ul>
<li><em>The C Programming Language, Second Edition</em>, Brian W. Kernighan
and Dennis M. Ritchie, Prentice Hall, 1988<br />
The definitive C book.
</li>
</ul>
<h4 id="python">Python</h4>
<ul>
<li><em>Core Python Programming, Second Edition</em>, Wesley J. Chun,
Prentice Hall, September 18, 2006<br />
Usually I like O'Reilly books best, but I slightly prefer Chun's text
to <em>Learning Python</em>.<br />
Available at
<a href="http://search.safaribooksonline.com/0132269937?tocview=true">
Safari Books Online</a>
</li>
<li><em>The Django Book</em>, Apress, December 2007<br />
I think this is the first official Django book.<br />
</li>
</ul>
<h4 id="sqlite">SQLite</h4>
<ul>
<li><em>The Definitive Guide to SQLite</em>, Mike Owens, Apress, May 2006<br />
I browsed a few SQL books but liked this one better than most.
It has a good theory section.<br />
Available at
<a href="http://apress.com/book/view/9781590596739">
Apress.com</a>
</li>
</ul>
<h4 id="linux">Linux or related</h4>
<ul>
<li><em>X Power Tools</em>, Chris Tyler, O'Reilly, December 15, 2007<br />
Lots of good information on the X Window System and more; easy to understand.
I wish the basic Ubuntu or
Linux books had some of this information.<br />
Available at
<a href="http://search.safaribooksonline.com/9780596101954?tocview=true">
Safari Books Online</a>
</li>
<li>
<em>SSH, The Secure Shell: The Definitive Guide, Second Edition</em>,
Daniel J. Barrett; Richard E. Silverman; Robert G. Byrnes,
O'Reilly, May 10, 2005<br>
Available at
<a href="http://search.safaribooksonline.com/book/networking/ssh/0596008953">
Safari Books Online</a>
</li>
</ul>
<h4 id="ruby">Ruby</h4>
<ul>
<li><em>why's poignant guide to Ruby</em> by <a href="http://whytheluckystiff.net/">why
the lucky stiff</a><br />
Only in chapter 3, but very funny.<br />
Available free online at: <a href="http://poignantguide.net/ruby/">http://poignantguide.net/ruby/</a>
</li>
</ul>
<h4 id="javascript">Javascript</h4>
<ul>
<li>
<em>Javascript: The Good Parts</em>, Douglas Crockford, O'Reilly, May 2008<br>
This is the first Javascript book I read as I switched to Javascript and Frontend development. I was debating whether to read it since it was so old but I found it to be good and recommend it.
</li>
<li>
<em>You Don't Know JS: ES6 & Beyond</em>, Kyle Simpson, O'Reilly, December 2015<br>
This is the primary way I learned ES6. It is very detail oriented.<br>
Available free online
<a href="https://github.com/getify/You-Dont-Know-JS/tree/master/es6%20%26%20beyond">
on github
</a>
</li>
<li>
<em>JavaScript Allongé, the "Six" Edition</em>, Reg "raganwald" Braithwaite, Leanpub, 2016<br>
This was the third Javascript book I read and it was the most fun. It teaches functional programming concepts such as closures, shadowing, higher order functions, combinators, decorators, etc using ES6.<br>
Available free online at:
<a href="https://leanpub.com/javascriptallongesix/read">https://leanpub.com/javascriptallongesix/read</a>
</li>
</ul>
<h4 id="non-technical">Non technical</h4>
<ul>
<li>The Hitchhiker's Guide to the Galaxy by Douglas Adams</li>
<li>Calvin and Hobbes by Bill Watterson</li>
<li>Crime and Punishment by Fyodor Dostoyevsky</li>
<li>Mere Christianity by C. S. Lewis</li>
<li>Screwtape Letters by C. S. Lewis</li>
</ul>
<h4 id="see-also">See also</h4>
<ul>
<li><a href="http://www.reddit.com/r/books/comments/ch0wt/a_reading_list_for_the_selftaught_computer/">
http://www.reddit.com/r/books/comments/ch0wt/a_reading_list_for_the_selftaught_computer/</a></li>
<li><a href="http://stackoverflow.com/questions/1711/what-is-the-single-most-influential-book-every-programmer-should-read">
http://stackoverflow.com/questions/1711/what-is-the-single-most-influential-book-every-programmer-should-read</a></li>
<li><a href="http://www.reddit.com/r/books/comments/cq4qe/reddits_bookshelf/">
http://www.reddit.com/r/books/comments/cq4qe/reddits_bookshelf/</a></li>
</ul>
Django project #3: Creating models
2007-11-28T23:55:00-08:00https://www.saltycrane.com/blog/2007/11/django-project-3-creating-models/
<p>This section in the tutorial was actually very
straightforward. Here is a record of what I did. <span id="fullpost">I first created a
polls app.
</span><pre>sofeng@tortoise:~/Web/mysite$ python manage.py startapp polls
sofeng@tortoise:~/Web/mysite$ ll
total 92
-rw-r--r-- 1 0 2007 11/09 21:42 __init__.py
-rw-r--r-- 1 131 2007 11/09 21:56 __init__.pyc
-rwxr-xr-x 1 542 2007 11/09 21:42 manage.py
-rw-r--r-- 1 35840 2007 11/28 23:07 mydb
-rw-r--r-- 1 25600 2007 11/14 23:13 mydb_backup
drwxr-xr-x 2 4096 2007 11/28 23:15 polls
-rw-r--r-- 1 2886 2007 11/28 22:41 settings.py
-rw-r--r-- 1 1873 2007 11/28 22:42 settings.pyc
-rw-r--r-- 1 224 2007 11/28 22:44 urls.py
-rw-r--r-- 1 302 2007 11/28 22:45 urls.pyc
sofeng@tortoise:~/Web/mysite$ ll polls
total 8
-rw-r--r-- 1 0 2007 11/28 23:15 __init__.py
-rw-r--r-- 1 57 2007 11/28 23:15 models.py
-rw-r--r-- 1 26 2007 11/28 23:15 views.py</pre>
I entered the following into <code>polls/models.py</code>
<pre>from django.db import models
class Poll(models.Model):
question = models.CharField(maxlength=200)
pub_date = models.DateTimeField('date published')
class Choice(models.Model):
poll = models.ForeignKey(Poll)
choice = models.CharField(maxlength=200)
votes = models.IntegerField()</pre>
I edited settings.py to include the new app:
<pre>INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'mysite.polls',
)</pre>
I ran <code>python manage.py sql polls</code> to see the SQL CREATE
TABLE statements for the polls app.
<pre>sofeng@tortoise:~/Web/mysite$ python manage.py sql polls
BEGIN;
CREATE TABLE "polls_poll" (
"id" integer NOT NULL PRIMARY KEY,
"question" varchar(200) NOT NULL,
"pub_date" datetime NOT NULL
);
CREATE TABLE "polls_choice" (
"id" integer NOT NULL PRIMARY KEY,
"poll_id" integer NOT NULL REFERENCES "polls_poll" ("id"),
"choice" varchar(200) NOT NULL,
"votes" integer NOT NULL
);
COMMIT;</pre>
I ran <code>python manage.py syncdb</code> to create the model
tables in the database.
<pre>sofeng@tortoise:~/Web/mysite$ python manage.py syncdb
Creating table polls_poll
Creating table polls_choice
Installing index for polls.Choice model
Loading 'initial_data' fixtures...
No fixtures found.</pre>
I played with the shell.
<pre>sofeng@tortoise:~/Web/mysite$ python manage.py shell
Python 2.5.1 (r251:54863, Oct 5 2007, 13:36:32)
[GCC 4.1.3 20070929 (prerelease) (Ubuntu 4.1.2-16ubuntu2)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from mysite.polls.models import Poll, Choice
>>> Poll.objects.all()
[]
>>> from datetime import datetime
>>> p = Poll(question="What's up?", pub_date=datetime.now())
>>> p.save()
>>> p.id
1
>>> p.question
"What's up?"
>>> p.pub_date
datetime.datetime(2007, 11, 29, 1, 34, 4, 883118)
>>> p.pub_date = datetime(2005, 4, 1, 0, 0)
>>> p.save()
>>> Poll.objects.all()
[<poll: poll="Poll" object="object">]
>>> </poll:></pre>
I edited <code>polls/models.py</code> so that it looked like this:
<pre>import datetime
from django.db import models
class Poll(models.Model):
question = models.CharField(maxlength=200)
pub_date = models.DateTimeField('date published')
def __str__(self):
return self.question
def was_published_today(self):
return self.pub_date.date() == datetime.date.today()
class Choice(models.Model):
poll = models.ForeignKey(Poll)
choice = models.CharField(maxlength=200)
votes = models.IntegerField()
def __str__(self):
return self.choice</pre>
I went back to the shell.
<pre>sofeng@tortoise:~/Web/mysite$ python manage.py shell
Python 2.5.1 (r251:54863, Oct 5 2007, 13:36:32)
[GCC 4.1.3 20070929 (prerelease) (Ubuntu 4.1.2-16ubuntu2)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from mysite.polls.models import Poll, Choice
>>> Poll.objects.all()
[<Poll: What's up?>]
>>> Poll.objects.filter(id=1)
[<Poll: What's up?>]
>>> Poll.objects.filter(question__startswith='What')
[<Poll: What's up?>]
>>> Poll.objects.get(pub_date__year=2005)
<Poll: What's up?>
>>> Poll.objects.get(id=2)
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/var/lib/python-support/python2.5/django/db/models/manager.py", line 73, in get
return self.get_query_set().get(*args, **kwargs)
File "/var/lib/python-support/python2.5/django/db/models/query.py", line 252, in get
raise self.model.DoesNotExist, "%s matching query does not exist." % self.model._meta.object_name
DoesNotExist: Poll matching query does not exist.
>>> Poll.objects.get(pk=1)
<Poll: What's up?>
>>> p = Poll.objects.get(pk=1)
>>> p.was_published_today()
False
>>> p = Poll.objects.get(pk=1)
>>> p.choice_set.create(choice='Not much', votes=0)
<Choice: Not much>
>>> p.choice_set.create(choice='The sky', votes=0)
<Choice: The sky>
>>> c = p.choice_set.create(choice='Just hacking again', votes=0)
>>> c.poll
<Poll: What's up?>
>>> p.choice_set.all()
[<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]
>>> p.choice_set.count()
3
>>> Choice.objects.filter(poll__pub_date__year=2005)
[<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]
>>> c = p.choice_set.filter(choice__startswith='Just hacking')
>>> c.delete()
>>>
</pre>
That's it for now. Next time I will work with Django's admin web interface.</p>
Django project #2: SQLite setup
2007-11-15T00:26:00-08:00https://www.saltycrane.com/blog/2007/11/django-project-2-sqlite-setup/
<p>In the <a href="http://www.saltycrane.com/blog/2007/11/django-project-1-install/">first installment</a> of the <em>Sample Django Project</em>, I installed Django and created a project. In this installment, I will set up the SQLite database. At first, I thought I had to figure out what kind of data to put in the database, but in actuality, I can create an empty database and fill it in later. That is what I am doing. Here are the steps.</p>
<ol>
<li>Install SQLite 3
<pre>sofeng@tortoise:~$ sudo apt-get install sqlite3</pre>
</li>
<li>Edit settings.py<br />
<kbd>cd</kbd> to the <em>mysite</em> directory created last time.
<pre>sofeng@tortoise:~$ cd ~/Web/mysite</pre>
Edit <code>settings.py</code> and change the following 2 lines
<pre>DATABASE_ENGINE = 'sqlite3'
DATABASE_NAME = '/home/sofeng/Web/mysite/mydb'</pre>
</li>
<li>Create Django tables in the database
<pre>sofeng@tortoise:~/Web/mysite$ 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
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 'sofeng'):
E-mail address: youremail@yourhost.com
Password:
Password (again):
Superuser created successfully.
Installing index for auth.Message model
Installing index for auth.Permission model
Loading 'initial_data' fixtures...
No fixtures found.</pre>
</li>
<li>Take a look at the databases created
<pre>sofeng@tortoise:~/Web/mysite$ sqlite3 mydb
SQLite version 3.4.2
Enter ".help" for instructions
sqlite> .schema</pre>
You should see a bunch of CREATE TABLE statements. If you get the following error message, it probably means you used sqlite instead of sqlite3.
<pre>Unable to open database "mydb": file is encrypted or is not a database</pre>
</li>
</ol>
<p>Well that was pretty easy. Next time, we'll create some models and actually write some python code.</p>
Django project #1: Install
2007-11-09T22:28:00-08:00https://www.saltycrane.com/blog/2007/11/django-project-1-install/
<p>I plan to do a side project in Django just to get some web programming experience. I'm not sure exactly what the site will be yet. Maybe my very own blog software with extensive tagging capabilities and maybe revision control. I also have an idea for a site to specialize in comparison searching. E.g. "python vs. ruby", "mac vs. linux", "kde vs. gnome", etc. Or, if I find someone who needs a website, I might work someone else's project. As long as I get to choose the technology. Anyways, I plan to document my steps here. I've already installed Ubuntu. Here are my steps in creating my Django website.</p>
<h4>Install django</h4>
<pre>$ sudo apt-get install python-django</pre>
<br />
<h4>Test install</h4>
<p>To test the install, I typed <em>import django</em> in the python interpreter.
<pre>$ python
Python 2.5.1 (r251:54863, Oct 5 2007, 13:36:32)
[GCC 4.1.3 20070929 (prerelease) (Ubuntu 4.1.2-16ubuntu2)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import django
>>> </pre>
Alright, It worked.</p>
<br /><br />
<h4>Create a project</h4>
<p>I tried to follow the <a href="http://www.djangoproject.com/documentation/0.96/tutorial01/"> Django tutorial</a> and ran the django-admin.py command. However, I got the <em>command not found</em> error message.
<pre>$ django-admin.py startproject mysite
bash: django-admin.py: command not found</pre>
I googled for the error message and found <a href="http://code.djangoproject.com/wiki/InstallationPitfalls#django-admin.pydoesnotwork">this link</a> which said to link the django-admin.py file to a location in your path.
<pre>$ sudo ln -s /usr/lib/python-django/bin/django-admin.py /usr/local/bin/django-admin.py</pre>
I tried the django-admin.py command again, and now got a <em>Permission denied</em> error.
<pre>$ django-admin.py startproject mysite
bash: /usr/local/bin/django-admin.py: Permission denied</pre>
Googling again, I found <a href="http://www.djangoproject.com/documentation/tutorial01/">the SVN release version of the tutorial</a> which gave instructions about the <em>Permission denied</em> error. (I was using the 0.96 version of the tutorial). So I changed the permissions of the django-admin.py file to be executable.
<pre>$ sudo chmod +x /usr/lib/python-django/bin/django-admin.py</pre>
I tried the command again.
<pre>$ django-admin.py startproject mysite</pre>
Alright, success! I got no errors.
</p>
<br /><br />
<h4>Start the development web server</h4>
<pre>$ cd mysite
$ python manage.py runserver
Validating models...
0 errors found.
Django version 0.96, using settings 'mysite.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.</pre>
Alright, it worked! I know you are excited. I visited http://127.0.0.1:8000/ in my browser and got a nice page affirming me of my success.<br />
<p><big><strong>It worked!</strong></big><br />
<strong>Congratulations on your first Django-powered page.</strong></p>
This seems like a good stopping point. The next step is setting up the database.<br />
My software tools list
2007-08-10T13:32:00-07:00https://www.saltycrane.com/blog/2007/08/current-configuration/<style type="text/css">
td {
vertical-align: top;
}
</style>
<p>Inspired by Mark Pilgrim's
<a href="http://web.archive.org/web/20101214224709/http://diveintomark.org/archives/2006/06/26/essentials-2006">
2006 Essentials list</a>,
below is a list of my current software tools. If you notice a lot of
"I switched from ..." statements, keep in mind that I am a
<a href="http://undefined.com/ia/2006/10/10/the-fourteen-types-of-programmers-type-2-those-that-like-shiny-things/">
programmer who likes shiny things</a>.
</p>
<h4 id="other-lists">Other lists</h4>
<ul>
<li><a href="http://web.archive.org/web/20101214234142/http://diveintomark.org/archives/2008/10/28/essentials-2008">
Mark Pilgrim's Essentials, 2008 edition</a>
</li>
<li><a href="http://web.archive.org/web/20081226033542/http://adam.gomaa.us/blog/essentials-cop-out/">
Adam Gomaa's Essentials (2008)</a>
</li>
<li>Here is a <a href="http://mark.pilgrim.usesthis.com/">2010 update
for Mark Pilgrim</a>. After 2 and a half years, I still have several items
in common: <a href="#operating-system">Ubuntu</a> running <a href="#terminal">rxvt-unicode</a>
and <a href="#editor">Emacs 23</a>, the <a href="#keyboard">Unicomp keyboard</a>,
an <a href="#mobile-phone">Android phone</a>, <a href="#email">Gmail</a>,
Google Reader, Google Docs, and Pandora.
</li>
<li><a href="http://salvatore.sanfilippo.usesthis.com/">Salvatore Sanfilippo on usesthis.com (2011)</a></li>
<li><a href="http://aaron.boodman.usesthis.com/">Aaron Boodman on usesthis.com (2011)</a></li>
</ul>
<h4 id="contents">Contents</h4>
<table>
<tr>
<td>
<ul>
<li><a href="#operating-system">Operating System</a>: Ubuntu</li>
<li><a href="#window-manager">Window Manager</a>: Qtile</li>
<li><a href="#editor">Editor</a>: Emacs</li>
<li><a href="#terminal">Terminal</a>: urxvt + screen</li>
<li><a href="#vcs">Version Control System</a>: Git</li>
<li><a href="#high-high-level-language">"High-high-level" Language</a>: Python</li>
<li><a href="#web-framework">Web Framework</a>: Django/Flask</li>
</ul>
</td>
<td>
<ul>
<li><a href="#web-browser">Web Browser</a>: Firefox</li>
<li><a href="#email">Email</a>: Gmail</li>
<li><a href="#graphical-diff">Graphical Diff</a>: KDiff3</li>
<li><a href="#keyboard">Keyboard</a>: Leopold Tenkeyless (MX Browns)</li>
<li><a href="#office-chair">Office Chair</a>: Undecided</li>
<li><a href="#mobile-phone">Mobile Phone</a>: Motorola Droid 4</li>
</ul>
</td>
</tr>
</table>
<h4 id="my-list">My software tools list</h4>
<table style="font-size:100%">
<tr>
<td ><b>Category</b></td>
<td ><b>Currently using</b></td>
<td ><b>Comments</b></td>
</tr>
<tr>
<td id="operating-system" ><p>Operating System</p>
<a href="#operating-system" title="Section permalink" class="sectionlink">¶</a></td>
<td ><p><a href="http://www.ubuntu.com">Ubuntu</a> 14.04 Trusty Tahr</p>
</td>
<td >
<p>My first
Ubuntu install was in 2007 (dual-boot) and I got my first Windows-free machine
in 2008. I have used Cygwin
and coLinux when on Windows. Cygwin integrates better with Windows applications,
but coLinux is super fast and allows you to run a full Linux distro on top of
Windows. Windows does have some advantages, but overall I prefer Linux.</p>
<p><em>Update 2008-09</em>: Switching jobs means I can now use Linux at work.
I'm now using Linux about 90% of the time. My wife still uses Windows Vista on our laptop.</p>
<p>I don't have enough experience
with OSX to draw any authoritative conclusions, though I think
<a href="http://en.wikipedia.org/wiki/Mark_Pilgrim">Mark Pilgrim</a>
has <a href="http://diveintomark.org/archives/2006/06/02/when-the-bough-breaks">
biased me against Apple</a>. Also, I think Linux's
<a href="http://c2.com/cgi/wiki?FreeAsInBeer">free as in beer</a> (and somewhat
related free as in speech) characteristics
vs. Mac's expensive (and somewhat related proprietary) characteristics
resonate with the cheap engineer in me.</p>
<p>Additional Linux vs. Mac commentary:</p>
<ul>
<li>
<a href="http://en.wikipedia.org/wiki/Jamie_Zawinski">JWZ</a> 2000:
<a href="http://www.jwz.org/doc/linux.html">Unix/linux sucks less, but it still sucks.</a>
</li>
<li>
JWZ <a href="http://jwz.livejournal.com/494040.html">2005</a>,
<a href="http://jwz.livejournal.com/780264.html">2007</a>: Linux sucks more.
</li>
<li>
<a href="http://en.wikipedia.org/wiki/Paul_Graham">Paul Graham</a> 2005:
<a href="http://www.paulgraham.com/mac.html">Return of the Mac.</a>
</li>
<li>
Mark Pilgrim 2006: <a href="http://diveintomark.org/archives/2006/05/30/bye-apple">
Bye, Apple</a>
</li>
<li>
<a href="http://en.wikipedia.org/wiki/Cory_Doctorow">Cory Doctorow</a> 2006:
<a href="http://www.boingboing.net/2006/06/29/mark-pilgrims-list-o.html">
Me too</a>.
</li>
<li>
<a href="http://en.wikipedia.org/wiki/Tim_Bray">Tim Bray</a> 2006:
<a href="http://www.tbray.org/ongoing/When/200x/2006/06/15/Switch-From-Mac">Me too</a>,
<a href="http://www.tbray.org/ongoing/When/200x/2006/08/16/Back-to-the-Mac">Back to the Mac</a>
</li>
<li>
Steve Yegge 2008:
<a href="http://steve-yegge.blogspot.com/2008/04/settling-osx-focus-follows-mouse-debate.html">
Switching to OSX for the fonts</a>
</li>
<li>
Of course, <a href="http://en.wikipedia.org/wiki/Linus_Torvalds">Linus Torvalds</a>
<a href="http://www.smh.com.au/news/technology/q-and-a-with-linus-torvalds/2008/02/05/1202090403120.html?page=2">
"prefers" Linux</a> (2008)
</li>
<li><a href="http://en.wikipedia.org/wiki/Donald_Knuth">Donald Knuth</a>,
author of <em>The Art of Computer Programming</em>,
<a href="http://www.informit.com/articles/printerfriendly.aspx?p=1193856">
uses Ubuntu Linux for work, and Macs for play</a> (2008).
</li>
<li>Salvatore Sanfilippo (author of <a href="http://code.google.com/p/redis/">Redis</a>)
chooses to use
a Mac so he can <em>"focus on what <he's> doing without spending hours trying to
fix unrelated desktop stuff"</em> but says Linux is
<a href="http://antirez.com/post/linux-better-for-coding.html">
<em>"still better for coding"</em></a> (2009).
</li>
<li>Ted Dziuba: <a href="http://harmful.cat-v.org/software/operating-systems/osx/osx-unsuitable-web-development">
<em>MacOS X is an Unsuitable Platform for Web Development</em>
</a> (2011)</li>
<li>Bozhidar Batsov: <a href="http://batsov.com/Linux/Windows/Rant/2011/06/11/linux-desktop-experience-killing-linux-on-the-desktop.html">
<em>The Linux desktop experience is killing Linux on the desktop</em>
</a> (2011)</li>
</ul>
<p>History: 1987: <a href="http://en.wikipedia.org/wiki/Apple_IIGS">Apple GS/OS</a>,
1994: <a href="http://en.wikipedia.org/wiki/Windows_3.1x">Windows 3.1</a>,
2000: <a href="http://en.wikipedia.org/wiki/Windows_98">Windows 98</a>,
2001: <a href="http://en.wikipedia.org/wiki/Windows_XP">Windows XP</a>,
2007: <a href="http://en.wikipedia.org/wiki/Windows_Vista">Windows Vista</a>,
2007: <a href="http://en.wikipedia.org/wiki/Ubuntu_%28operating_system%29">Ubuntu</a>
</p>
</td>
</tr>
<tr>
<td id="window-manager"><p>Window Manager</p>
<a href="#window-manager" title="Section permalink" class="sectionlink">¶</a></td>
<td ><p><a href="http://www.qtile.org/">Qtile</a></p></td>
<td >
<p>The WM written in Python,
<a href="http://panela.blog-city.com/fun_of_tiling_window_managers.htm">
recommended by Matt Harrison</a>.
</p>
<p>Previously, wmii: dynamic, tiling, scriptable window manager that doesn't
require a mouse. It sucks less.</p>
<p>I switched from <a href="http://www.nongnu.org/ratpoison/">ratpoison</a>
at the same time I started using coLinux because running native Linux
allowed me to use any Linux window manager as well.</p>
<p>Recently, some have switched from wmii to
<a href="http://xmonad.org/">xmonad</a>, the new
<a href="http://www.haskell.org/">Haskell</a> tiling window manager.
It has some nice features over wmii, including dual head support, but after
a brief excursion, I slightly prefer wmii's way of doing things.</p>
<p>If you're a hard core Lisper,
<a href="http://www.nongnu.org/stumpwm/">stumpwm</a> is the window manager
for you. It has a <a href="http://en.wikipedia.org/wiki/REPL">REPL</a>. This one
seems a little too hard core for me, especially since I don't know Lisp.</p>
<p>History: 2007: ratpoison, 2007: wmii, 2012: Qtile</p>
</td>
</tr>
<tr>
<td id="editor"><p>Editor/IDE</p>
<a href="#editor" title="Section permalink" class="sectionlink">¶</a></td>
<td><p><a href="http://www.gnu.org/software/emacs">GNU Emacs</a> 24.3</p></td>
<td >
<p>Switched from Eclipse in 2007. It was a slow transition, but
I think Emacs is worth the investment.</p>
<p><b>On Emacs vs. Vim:</b> I really like that Vim can be used easily on remote
machines. Currently, I use TRAMP for remote file editing. It is very cool,
but it is slow, and I sometimes fall back on nano (*gasp*) for a quick
config file edit when I am logged into a remote terminal. I also suspect
that the dual modes of Vim are more efficient than Emacs-- I'm just not
smart enough to get used to it. Maybe someday I will switch to
<a href="http://www.gnu.org/software/emacs/manual/html_node/viper/index.html#Top">Viper
mode</a>. Why Emacs over Vim? I choose Emacs because it is more powerful
and closer to an IDE. Emacs Lisp allows you to do whatever you want.
</p>
<p><b>On Emacs vs. Eclipse:</b> I found Eclipse was too slow and heavy, used too
much screen real estate, and was too difficult to customize. Scripting
Eclipse required writing extensions in Java whereas Emacs can be extended
in elegant Lisp. I think Eclipse (and other IDE's) are better for code
exploration and they probably have features that I'm not aware of since
I don't use them. I figure, though, that with enough Emacs Lisp, Emacs
can do anything those IDEs can do. If you really want those features,
it's a question of if you want to spend the time achieving perfection
in Emacs or getting real work done in another IDE. I choose perfection. ;)
</p>
<p>Additional commentary:</p>
<ul>
<li><a href="http://blog.ianbicking.org/the-vs-emacs.html">
THE vs. Emacs</a>, Ian Bicking (2005)</li>
<li><a href="http://steve.yegge.googlepages.com/effective-emacs">
Effective Emacs</a>, Steve Yegge (2005)</li>
<li><a href="http://blog.ianbicking.org/other-editors.html">
Other Editors?</a>, Ian Bicking (2005)</li>
<li><a href="http://glyph.twistedmatrix.com/2008/04/structured-python-editor.html">
Structured Python Editor</a>, Glyph Lefkowitz (2008)</li>
<li><a href="http://chalain.livejournal.com/74234.html">
Editor Wars: Revenge of the... oh, whatever.</a>, Chalain (2008)</li>
<li><a href="http://metajack.im/2008/09/05/what-you-can-learn-from-emacs/">
What You Can Learn From Emacs</a>, Jack Moffitt (2008)</li>
<li><a href="http://glyph.twistedmatrix.com/2008/12/emacs-test.html">
The Emacs Test</a>, Glyph Lefkowitz (2008)</li>
<li><a href="http://www.redreddesign.com/blog/emacs-extensions-i-cant-live-without/">
emacs extensions i can’t live without</a> (2009)</li>
</ul>
<p>Screencasts:</p>
<ul>
<li><a href="http://platypope.org/yada/emacs-demo/">I need a cool European accent</a></li>
<li><a href="http://www.vimeo.com/1013263">What You Can Learn From ido.el</a></li>
<li><a href="http://www.youtube.com/watch?v=76Ygeg9miao#t=01m47s">YASnippet demo</a></li>
</ul>
<p>Install method:
<del><a href="http://www.saltycrane.com/blog/2008/10/installing-emacs-23-cvs-ubuntu-hardy/">
from source</a></del>
<code>sudo apt-get install emacs</code>
</p>
<p>Useful packages:</p>
<ul>
<li><a href="http://www.gnu.org/software/tramp/">
Tramp</a>: transparent remote file access</li>
<li><a href="http://www.emacswiki.org/emacs/InteractivelyDoThings">
ido</a>: Buffer switching and more</li>
<li><a href="http://orgmode.org/">Org-Mode</a>: note taking, task lists</li>
<li><a href="http://github.com/yoshiki/yaml-mode">yaml-mode</a>: for yaml</li>
<li><a href="http://jblevins.org/projects/markdown-mode/">markdown-mode</a>: for markdown</li>
<li><a href="http://code.google.com/p/yasnippet/">YASnippet</a>:
Textmate-inspired templating. Great for making HTML less tedious.</li>
<li><a href="https://github.com/nonsequitur/smex">smex: IDO for M-x</a></li>
<li><a href="https://github.com/magnars/multiple-cursors.el">multiple-cursors</a>: this is really fun *and* useful.</li>
<li><a href="http://web-mode.org/">web-mode</a>: for HTML/CSS/Javascript</li>
</ul>
<p>My emacs config on github: <a href="http://github.com/saltycrane/emacs">http://github.com/saltycrane/emacs</a></p>
<p>History: 2001: Emacs/NEdit/UltraEdit, 2006: Eclipse, 2007: Emacs</p>
</td>
</tr>
<tr>
<td id="terminal"><p>Terminal</p>
<a href="#terminal" title="Section permalink" class="sectionlink">¶</a></td>
<td><p><a href="http://software.schmorp.de/pkg/rxvt-unicode.html">urxvt</a></p>
9.19
+ <br><a href="http://www.gnu.org/software/screen/">screen</a></td>
<td>
<p>urxvt supports xft (anti-aliased) fonts, real transparency (not that
I actually use transparency with Qtile), and fading (which I do use with Qtile)
and it is much faster and lighter than gnome-terminal or konsole. screen allows me to switch
terminal sessions without ugly tabs, attach to remote sessions, search through
the scrollback buffer, and more. <em>Update:</em> urxvt also has embedded perl.</p>
<p>Install method:
<del><a href="/blog/2009/11/how-make-urxvt-look-gnome-terminal/">from source</a></del>
<code>sudo apt-get install rxvt-unicode</code>
</p>
</td>
</tr>
<tr>
<td id="vcs"><p>Version Control System</p>
<a href="#vcs" title="Section permalink" class="sectionlink">¶</a></td>
<td>
<p><a href="http://git-scm.com/">Git</a></p>
</td>
<td>
<p>Switched from Subversion to Mercurial in June 2007. The merging in Mercurial
is very nice and can be done without thinking. I do miss Subversion/Subclipse's
revision history viewer, file compare, and ability to isolate files apart from
changesets. <em>Update 2010-04-05:</em> See <a href="#c8633">my comment
below</a>.
</p>
<p><em>Update 2011-05:</em> Switched my personal repos to Git. Use Git almost
exclusively now.
</p>
<p>Additional commentary:</p>
<ul>
<li><a href="http://jonchu.posterous.com/16445171">
DVCS: Why I chose Mercurial over Git</a> (2010)</li>
<li><a href="http://blog.extracheese.org/2010/05/why-i-switched-to-git-from-mercurial.html">
Why I Switched to Git From Mercurial</a> (2010)</li>
</ul>
<p>Install method: <code>sudo apt-get install mercurial</code></p>
<p>History: 2001: SCCS, 2003: Other, 2007: Subversion, 2007: Mercurial,
2010: Mercurial/Git, 2011: Git</p>
</td>
</tr>
<tr>
<td id="high-level-language"><p>"High-level" Language</p>
<a href="#high-level-language" title="Section permalink" class="sectionlink">¶</a></td>
<td><p>None</p></td>
<td>
<p>Previously, I used C. Now I use don't use any statically-typed languague. Wouldn't mind learning <del>C++</del> Go. Although,
Linus <a href="http://thread.gmane.org/gmane.comp.version-control.git/57643/focus=57918">
doesn't like it.</a></p>
</td>
</tr>
<tr>
<td id="high-high-level-language" ><p>"High-high-level" Language</p>
<a href="#high-high-level-language" title="Section permalink" class="sectionlink">¶</a></td>
<td ><p><a href="http://www.python.org/">Python</a> 2.7</p></td>
<td >
<p>My love for Python is strong. I switched from Perl in 2005
and have no regrets. Object-oriented, easy to read (no more
<a href="http://en.wikipedia.org/wiki/There_is_more_than_one_way_to_do_it">
TIMTOWTDI</a>), and smart people use it. I also want to learn Javascript 2
becuase it is the
<a href="http://steve-yegge.blogspot.com/2007/02/next-big-language.html">
"Next Big Language"</a> and Lisp because it is the
<a href="http://www.paulgraham.com/avg.html">"most powerful language"</a>.</p>
<p><b>On Python vs. Ruby:</b> from what I've read, I characterize Ruby as the
more expressive language more similar to Perl (than Python is) and Python
as the more regimented language. Since I like regimented, I like Python.</p>
<p><b>On Python vs. Lisp:</b> I've concluded that I lack the intelligence to
harness enough of Lisp's power to counteract its non-practicality (e.g.
lack of libraries).</p>
<p>Additional commentary:</p>
<ul>
<li><a href="http://en.wikipedia.org/wiki/Paul_Graham">Paul Graham</a>:
<a href="http://www.paulgraham.com/icad.html">Python is
getting closer to Lisp</a> (2002)</li>
<li>Paul Prescod: <a href="http://www.prescod.net/python/IsPythonLisp.html">
no it isn't</a></li>
<li>Paraphrase of <a href="http://www.swiss.ai.mit.edu/~gjs/">
Gerald Jay Sussman</a> (one of the creators of Scheme):
<a href="http://blog.snowtide.com/2009/03/24/why-mit-now-uses-python-instead-of-scheme-for-its-undergraduate-cs-program">
Why MIT now uses python instead of scheme for its undergraduate CS program</a>
(2009)</li>
</ul>
<p>Install method: preinstalled on Ubuntu</p>
<p>History: 2001: <a href="http://www.perl.org/">Perl</a>,
2005: <a href="http://www.python.org/">Python</a>
</p>
</td>
</tr>
<tr>
<td id="web-framework" ><p>Web Framework</p>
<a href="#web-framework" title="Section permalink" class="sectionlink">¶</a></td>
<td >
<p>
<a href="http://www.djangoproject.com/">Django</a> /
<a href="http://flask.pocoo.org/">Flask</a>
</p>
</td>
<td >
<p><em>2014-10:</em>I now use Flask at work. Previously, I used Django. I think I like SQLAlchemy and Jinja2 better than Django. Django has a lot more built in and is put together better. Flask's thread locals are convenient and messy. Probably <a href="http://www.pylonsproject.org/">Pyramid</a> is the best but no one uses it.</p>
<p>Here are some links:</p>
<ul>
<li><a href="http://www.ianbicking.org/">Ian Bicking</a>:
<a href="http://blog.ianbicking.org/theres-so-much-more-than-rails.html">
There's so much more than Rails</a> (2005)</li>
<li><a href="http://blog.ianbicking.org/2008/01/12/what-php-deployment-gets-right/">
What PHP deployment gets right</a> (2008)</li>
<li><a href="http://www.feedmagnet.com/blog/django-vs-rails/">
Django vs. Rails</a> (2009)</li>
</ul>
<p>Install method: <code>pip install Django</code> / <code>pip install Flask</code></p>
</td>
</tr>
<tr>
<td id="web-browser"><p>Web Browser</p>
<a href="#web-browser" title="Section permalink" class="sectionlink">¶</a></td>
<td><p>Firefox</p></td>
<td >
<p>Not to be confused with <a href="http://www.konqueror.org">
Konqueror</a>, Conkeror is an emacs-like, keyboard driven, scriptable,
Mozilla-based web browser. I've used it almost full
time since January 2008. It is still considered alpha stage software
so there are a number of bugs. However, it is still pretty sweet. I use
Firefox as a backup (and IE Tab for Launchcast and Netflix on Windows).</p>
<p>Unfortunately, one of the annoying things in Firefox 2 is present in
Conkeror as well-- memory leaks. Based on
<a href="http://kb.mozillazine.org/Memory_Leak">this Mozilla article</a>
and some brief personal experience, Firefox 3 has made fixes in this area.
It would be nice if Conkeror could benefit from the Firefox 3 fixes.</p>
<p><em>Update 2010-05-04:</em> Conkeror is really awesome, but I had some memory/cpu issues with it
a while ago and haven't put in the work to merge my custom keybindings
with the latest Conkeror code. Also waiting to see if there will be
an Emacs version of <a href="http://vimium.github.com/">Vimium</a>
(since, after all <a href="http://vimperator.org/">vimperator</a>
followed conkeror).
</p>
<p>I'm currently using a combination of Google Chrome and Firefox.
Chrome is faster, especially for Javascript-intensive sites, but I really
<a href="http://superuser.com/questions/91334/alternative-to-arrow-keys-in-google-chrome-location-bar">
<em>hate</em> the Google Chrome Omnibar</a>. If you have a solution
or workaround, please post an answer. Firefox also has some Add-ons
that Chrome doesn't.
</td>
</tr>
<tr>
<td id="email" ><p>Email</p>
<a href="#email" title="Section permalink" class="sectionlink">¶</a></td>
<td ><p>Gmail</p></td>
<td >
<p><em>Update 2010-05-04:</em>
Gmail. It's slow but it has a lot of features and it's in
my web browser and it's easy and it works and I don't really like using
email that much anyways. Oh, and, big plus, syncing works almost perfect
with my Android phone. (Actual full switch to Gmail was around December 2008.)
</p>
</td>
</tr>
<tr>
<td id="graphical-diff" ><p>Graphical diff/merge</p>
<a href="#graphical-diff" title="Section permalink" class="sectionlink">¶</a></td>
<td ><p><a href="http://kdiff3.sourceforge.net/">KDiff3</a></p></td>
<td >
<p>I started using KDiff a while ago on Windows and have
always liked it. I'm thinking, though, since I'm an Emacs person, I ought to
use <a href="http://www.gnu.org/software/emacs/manual/html_mono/ediff.html">
Ediff</a>.</p>
<p>Install method: <code>sudo apt-get install kdiff3</code></p>
</td>
</tr>
<tr>
<td id="keyboard"><p>Keyboard</p>
<a href="#keyboard" title="Section permalink" class="sectionlink">¶</a></td>
<td>
<p><a href="http://pckeyboards.stores.yahoo.net//en104wh.html">
Unicomp Endurapro</a> /
<a href="http://elitekeyboards.com/products.php?sub=leopold,tenkeyless&pid=fc200rtab">
Leopold Tenkeyless w/ Cherry MX Browns
</a></p>
</td>
<td>
<p>I realize a keyboard is hardware and not software, but it is
hardware that can evoke strong opinions, so I made an exception.
</p>
<p>One problem with hardware is I can't try out all the different types
and choose the one I like the best as I can with software. So
my selection is based on reviews not on actual experience. And,
after buying the Endurapro, I found I actually did not like the
integrated mouse stick, so I should have bought a Customizer 104/105
and saved $30.</p>
<p><em>Update 2011-05:</em> Got the Leopold tenkeyless for work.
Tenkeyless is a must for quicker mouse access.
And I actually like the lighter touch of the
Cherry MX Browns versus the Unicomp's buckling springs. I like it
so much I want to replace my Unicomp at home with another Leopold.
</p>
<p><em>Update:</em> Got a second Leopold for home to replace my Unicomp.</p>
<p>Additional commentary:</p>
<ul>
<li><a href="http://www.jwz.org/gruntle/wrists.html">
my wrists and welcome to them.</a>, JWZ (1999)</li>
<li><a href="http://www.dansdata.com/deck.htm">
Deck keyboard</a>, Dan (2005)</li>
<li><a href="http://bc.tech.coop/blog/060131.html">
Surviving Emacs - Part 4</a>, Bill Clementson (2006)</li>
<li><a href="http://glyf.livejournal.com/55780.html">
Keyboard Fight</a>, Glyph Lefkowitz (2006)</li>
<li><a href="http://diveintomark.org/archives/2006/05/30/bye-apple">
Bye, Apple</a>, Mark Pilgrim (2006)</li>
<li><a href="http://glyph.twistedmatrix.com/2009/01/meandering-review-of-logitech.html">
A Meandering Review of the Logitech Illuminated Keyboard</a>,
Glyph Lefkowitz (2009)</li>
</ul>
<p>History: 2009: Unicomp Endurapro, 2011: Leopold Tenkeyless (MX Browns)</p>
</td>
</tr>
<tr>
<td id="office-chair"><p>Office Chair</p>
<a href="#office-chair" title="Section permalink" class="sectionlink">¶</a></td>
<td><p>Undecided</p></td>
<td><p>Herman Miller Aeron or Steelcase Leap</p>
</td>
</tr>
<tr>
<td id="mobile-phone"><p>Mobile Phone</p>
<a href="#mobile-phone" title="Section permalink" class="sectionlink">¶</a></td>
<td><p><a href="http://www.motorola.com/Consumers/US-EN/Consumer-Product-and-Services/Mobile-Phones/Motorola-DROID-US-EN">Motorola Droid 4</a></p></td>
<td>
<p>After almost 10 years (and 3 generations) of the Palm Treo, I got the
Motorola Droid.</p>
<p>The Droid is pretty awesome, but I don't like switching
to landscape mode to use the keyboard-- especially when toolbars and headers
take up half the screen. I think Palm and Blackberry got this form factor Right.
Just as I prefer the keyboard over the mouse on my PC, I want to use the keyboard
and D-pad over the touchscreen on my phone.
<em>Update 2010-05-04:</em> The keyboard is mediocre and use of the D-pad
center button sucks. My Palm Treo 650 D-pad worked much better.
</p>
<p>My reasons for choosing an
<a href="http://en.wikipedia.org/wiki/Android_%28operating_system%29">Android</a>
phone are: I <em>need</em> a physical keyboard.
I <em>want</em> to hack on my phone. I <em>think</em> Android has more potential than Palm's
<a href="http://en.wikipedia.org/wiki/WebOS">webOS</a>
or <a href="http://en.wikipedia.org/wiki/Maemo">Maemo</a>.</p>
<p>Useful apps:</p>
<ul>
<li><a href="http://code.google.com/p/connectbot/">Connectbot</a>
Secure shell (SSH) client for the Android platform. Use this all the time. Awesome.</li>
<li><a href="http://www.android.com/market/#app=listen">Listen</a>
Listen from Google Labs brings podcast search, subscribe, download and stream to your Android-powered device.</li>
<li><a href="http://mytracks.appspot.com/">My Tracks</a>
Record GPS tracks. Monitor your performance. Share your outdoor activities with friends.
Really enjoy this one. Love tracking my romping.
</li>
</ul>
<p><em>2010-05-04 Updates:</em></p>
<ul>
<li><a href="http://www.google.com/mobile/voice/">Google Voice</a>: Free text messages
and better voicemail. Another useful app. Makes voicemail suck a lot less.</li>
<li><a href="http://levelupstudio.com/foxyring">FoxyRing</a>: automatically
control the ringer volume based on ambient noise level. I was doubtful about this one,
but it actually works well for me.</li>
<li><a href="http://www.flixster.com/mobile/apps/android">Flixter (Movies)</a>:
Useful for checking movie showtimes.
</li>
<li><a href="http://www.youversion.com/mobile/android">Bible (YouVersion)</a>:
This version is pretty good. I miss my <a href="http://www.plkr.org/">Plucker</a>
created Bible on my Palm though. I wish there was something like Plucker for
Android.
</li>
<li>DroidLight: use the LED camera flash as a flashlight. Simple and useful.
Works much better than using the screen as a flashlight.
</li>
<li><a href="http://www.google.com/mobile/navigation/">Google Navigation</a>: I
don't need my Garmin anymore. (Each has advantages and disadvantages, but I
have found it to be a capable replacement.)</li>
<li>Just want to mention that the Calendar sucks. It is great that I can sync
with my Google calendar, but I really hate the Android Calendar UI.</li>
</ul>
<p>Additional Commentary/Links:</p>
<ul>
<li><a href="http://randomfoo.net/2009/06/15/thoughts-on-the-palm-pre-g2-and-iphone-3g">
Thoughts on the Palm Pre, G2, and iPhone 3G</a> Leonard Lin (2009 June)</li>
<li><a href="http://jwz.livejournal.com/1055120.html">
JWZ gets a Palm Pre</a> (2009 June)</li>
<li><a href="http://jwz.livejournal.com/1055369.html">
Emacs on Android</a> (2009 June)</li>
<li><a href="http://daringfireball.net/2009/08/the_android_opportunity">
The Android Opportunity</a>, John Gruber (2009 August)</li>
<li><a href="http://jwz.livejournal.com/1108212.html">
Dear Palm, it's just not working out</a>, JWZ (2009 October)</li>
</ul>
<p>History: 2002: <a href="http://en.wikipedia.org/wiki/Treo_180">Handspring Treo 180</a>,
2003: <a href="http://en.wikipedia.org/wiki/Treo_600">Handspring Treo 600</a>,
2005: <a href="http://en.wikipedia.org/wiki/Treo_650">Palm Treo 650</a>,
2009: <a href="http://en.wikipedia.org/wiki/Motorola_Droid">Motorola Droid</a>
2011: <a href="http://en.wikipedia.org/wiki/Droid_4">Motorola Droid 4</a>
</p>
</td>
</tr>
</table>