This chapter is not even close to being finished. In fact, I've just started it.
The code will not work until the newforms-admin branch is merged into trunk.

Checkbook

Electric Checkbook is a recently launched web application written using the Ruby on Rails development framework. The application is a result of the Rails Rumble, a web application programming competition that allowed participants 48 hours to develop their projects. Electric Checkbook finished as 1st runner up in the competition, and was developed by a three man team from New Leaders, LLC, a commercial web development company.

Electric Checkbook provides a simple way to organize financial information. Users can keep track of financial transactions from multiple accounts, view a ledger of recent transactions, summarize transactions by payee, and even share account information with others.

In this chapter we will construction a simplified version of Electric Checkbook, and document the development process. We will use this example to learn about relationships between models, accepting user input through forms, and the Django administration interface.

Please go to the Electric Checkbook website, and take a close look at the features. You may even want to create a free account to see what the site has to offer.

electric checkbook

After browsing through the set of features, we can start to form a picture of the database that drives this application. The fundamental unit of data that we will need to keep track of is a transaction. For each transaction that is entered into the application, we will at least need to know the following:

  • Date
  • Account
  • Number
  • Payee
  • Amount

In the first chapter of this tutorial we used a single model class to represent our data. In this case, we will take a slightly more advanced approach, using multiple models classes that are related to each other. We will try to follow the principles of database normalization, which can increase the performance of our application, reduce the amount of duplicated data that is stored in our database, and protect against data inconsistencies.

For example, if we used a single model class for our transactions we might mistakenly enter in Citibank Checking for the account that a certain transaction should be recorded against, and later enter in Citibank checking for a second transaction against that same account. If we then queried our database for all transactions involving Citibank Checking we would miss the second transaction which used a lowercase c to spell the word checking.

To avoid this problem in our checkbook application we can use a model class for Accounts and a separate model class for Transactions. Our Accounts model class might include the following fields:

  • Name
  • Account type
  • Account number
  • Starting balance
  • Current balance

The account field of the Transactions model is then linked to the Accounts model, so that when entering a transaction a user is limited to selecting from a list of existing accounts. This eliminates the possible mistake we described above, and also helps us avoid the duplication of data.

Since we are using the Django object-relational mapper, we specify the relationship between the models in our model class definitions. In our case we are using foreign keys because an account can have multiple transaction, but a transaction can only have one account, something which is called a many-to-one relationship.

To keep things simple we will ignore more complicated transactions, such as transfers, which move money from one account to another. We will not implement a user authentication system. Also, we will not create an independent model for payees, even though this would be appropriate practice if we wanted to normalize our data.

Start Coding

We will start coding by setting up a new project with the appropriate settings. I will assume that you have completed the first chapter of the tutorial and are familiar with the following steps.

After launching the portable Django environment by double-clicking start.bat, create a new project named checkbook:

django-admin startproject checkbook

Change into the checkbook directory, then start a new application named ledger:

python manage.py startapp ledger

Next, edit the database section in the settings.py file:

DATABASE_ENGINE = 'sqlite3'
DATABASE_NAME = 'checkbook.db'
DATABASE_USER = ''
DATABASE_PASSWORD = ''
DATABASE_PORT = ''

We need to add the ledger application to the list of installed applications in the settings.py file. We also need to add django.contrib.admin to the list of installed applications, so that we can make use of this feature with out project. Edit the INSTALLED_APPS section of the settings file so that it contains the following:

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.admin',
    'checkbook.ledger',
)

Don't remove the other applications included in this section by default, as they are needed to use the administration interface.

The developers of Django like to keep the core of the framework small and fast. Extra functionality, like the administration interface and the authorization framework, are provided though custom django applications that can be easily re-used in any project. If you would like to read more about the django.contrib applications, check out the add-ons section of the official documentation.

Models

We will now defined the models that we will use in our application. Change into the ledger directory, and edit the models.py file so that it contains the following:

from django.db import models

class Account(models.Model):
    account_name = models.CharField(max_length=40)
    account_type = models.CharField(max_length=40)
    account_number = models.CharField(max_length=40)
    account_slug = models.SlugField()
    starting_balance = models.DecimalField(max_digits=12, decimal_places=2)
    current_balance = models.DecimalField(max_digits=12, decimal_places=2)
    
class Transaction(models.Model):
    date = models.DateField()
    account = models.ForeignKey(Account)
    number = models.IntegerField()
    payee = models.CharField(max_length=40)
    amount = models.DecimalField(max_digits=12, decimal_places=2)

We have defined two classes. The Account class has three fields, and the Transaction class has five fields. Look closely at the following line in the Transaction class definition:

account = models.ForeignKey(Account)

Just like other field definitions, the ForeignKey type takes arguments. The first argument should be the name of the class that is related to the field being defined. In our example we want the Transaction class to have an account field, but we want that account field linked to an existing account, provided by the Account class. By convention we use a lowercase version of the related model as the field name.

You may also have noticed a SlugField, which we did not discuss when planning our data models. A slug is a text string comprised of letters only, with dashes or underscores replacing spaces. A slug field helps us create a URL friendly way to access our account information. For example, we may have an account with an account_name of "etrade" and an account_type of "checking". We cannot have the following URL:

http://www.checkbook.com/accounts/etrade checking/

Spaces are invalid in URLs. However, if we combing the account_name and account_type into a SlugField, we can type:

http://www.checkbook.com/accounts/etrade-checking/

This becomes important when we are designing our URLs, which we will do later in this chapter.

SQL

Planning our application in terms of model classes and relationships is convenient. It allows us to ignore SQL, and to function without thinking about how the rows and columns in our database are mapped to our model classes. However, we should have some idea about how the Django Object Relational Mapper implements our models and queries.

For example, most people familiar with relational databases will know that each row in a database, which would correlate to an instance of one of our model classes, will usually have a primary key. A primary key is a field in each row that must be unique. No two rows can have the same primary key, so often a primary key field is a assigned an integer value that is automatically incremented for each row in the database.

Even though we have not defined a primary key field in the Transaction model or in the Account model, Django has done this for us. To take a look at the inner workings of Django, change into the checkbook project directory, then type the following at the command line:

python manage.py sql ledger

You should see the following output:

BEGIN;
CREATE TABLE "ledger_account" (
    "id" integer NOT NULL PRIMARY KEY,
    "account_name" varchar(40) NOT NULL,
    "account_type" varchar(40) NOT NULL,
    "account_number" varchar(40) NOT NULL,
    "account_slug", varchar(50) NOT NULL,
    "starting_balance" decimal NOT NULL,
    "current_balance" decimal NOT NULL
)
;
CREATE TABLE "ledger_transaction" (
    "id" integer NOT NULL PRIMARY KEY,
    "date" date NOT NULL,
    "account_id" integer NOT NULL REFERENCES "ledger_account" ("id"),
    "number" integer NOT NULL,
    "payee" varchar(40) NOT NULL,
    "amount" decimal NOT NULL
)
;
COMMIT;

We are using the manage.py utility to display the raw SQL that Django creates to implement the models that we defined for our ledger application. Notice that primary keys were created for us by default. This behavior is often convenient, but can be overridden if needed.

The raw SQL displayed above has not been executed yet. To do that, we use the syncdb command, which we will do later in this chapter.

URLconfs

Now will design a preliminary set of URLs that we want our application to understand.

The first page users should see when navigating to our checkbook application is an overview of their accounts. This should have a simple URL, something like:

http://www.checkbook.com/accounts/

Detailed information about a specific account, which will display a list of recent transactions involving that account, can also have a simple URL:

http://www.checkbook.com/accounts/etrade-checking/

For now, we won't worry about the URLs we will need to allow users to enter or edit transactions. Let's create regular expression patterns to implement our initial set of URLs. Edit the urls.py file in your project directory:

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^accounts/$', 'checkbook.ledger.views.accounts_summary'),
    (r'^accounts/(?P<slug>[-\w]+)/$', 'checkbook.ledger.views.account_detail'),
)

The fist pattern contains a caret ^ and a dollar sign $. The caret indicates that the url must match the beginning of the regular expression, and the dollar sign indicates that the url must match the end of the regular expression string.

The second pattern is designed to match and capture a slug, which in this case is a string of one or more words separated by hyphens. Using the example from earlier in this chapter, consider the following URL request:

http://checkbook.com/accounts/etrade-checking

This request will match the second pattern, and etrade-checking will be captured and sent to the view function specified.

The second pattern also contains a named group which is captured by the regular expression. In Django a named group begins with ?P, and is contained within brackets. As we learned in the first chapter, the portion of the regular expression in parentheses is sent to the view function designated in the pattern. By providing a name for the captured text, in this case ?P, our view function can access this variable by the given name, without worrying about the position in the view function definition.

The benefits of named groups are debatable, but they may allow for more readable source code at the sake of brevity. Chapter 8 of the Django Book has more information on the use of named groups in Django. Please note that when it comes to matching the regular expression against a URL, the text designating the named group is ignored, and it is not sent to the view function.

Views

Now we will create our basic view functions, which will handle the URL requests that we have specified. Edit the views.py file in the ledger application directory:

from django.shortcuts import render_to_response, get_list_or_404
from checkbook.ledger.models import Account, Transaction

def accounts_summary(request):
	account_list = Account.objects.all()
	return render_to_response('account_list.html', {'account_list': account_list})
    
def account_detail(request, slug):
    transaction_list = get_list_or_404(Transaction, account__account_slug = slug)
    return render_to_response('transaction_list.html', {'transaction_list' : transaction_list})

The accounts_summary view function uses the object.all() method to retrieve all of the Account objects from the database. This list is then passed into a template, which we have not yet written.

The account_detail view function takes the slug from the requested URL, and uses it to query the database for transactions which match the account specified. The list of matching transactions are then passed into a template.

Templates

Create a templates directory in the checkbook project directory. Change into the templates directory and create a template named account_list.html, which contains the following:

<table border=1>
<tr>
<td>Account Name</td><td>Balance</td>
</tr>
{% for account in account_list %}
<tr>
<td>{{account.account_name}} {{account.account_type}}</td> 
<td>{{ account.current_balance }}</td>
</tr>
{% endfor %}
</table>

Now create a template named transaction_list.html, which contains the following:

<table border=1>
<tr>
<td>Date</td><td>Amount</td>
</tr>
{% for transaction in transaction_list %}
<tr>
<td>{{transaction.date}}</td> 
<td>{{ transaction.amount }}</td>
</tr>
{% endfor %}
</table>

These templates accept the data passed to them from the corresponding view functions, and substitute the data according to the template tags provided.

These are very simple templates, which provide the bare minimum in functionality. Later in the development process we will enhance their usability and appearance, but they will do for now.

Don't forget to edit the settings.py file to include the path to your templates:

TEMPLATE_DIRS = (
  './templates/'
)

At this point we have the minimum functionality needed to view the data our application will store. We are still missing the elements we need to allow our user to input data, which we will tackle that later in this chapter.

Administration Interface

Django's administration interface allows trusted users low level access to the data stored in a project's database. Objects in the database can be added, deleted, and edited.

Enabling the administration interface takes very little effort. We have already added django.contrib.admin to our list of installed applications in our settings.py file. We will also need to make small changes to our urls.py and models.py files.

Edit your urls.py file so that it contains the following:

from django.conf.urls.defaults import *
from django.contrib import admin

urlpatterns = patterns('',
    (r'^accounts/$', 'checkbook.ledger.views.accounts_summary'),
    (r'^accounts/(?P<slug>[-\w]+)/$', 'checkbook.ledger.views.account_detail'),
    (r'^admin/(.*)', admin.site.root),
)

We have added an import statement which allows us to work with the admin interface. We have also added a pattern to our list of regular expressions, which forwards the appropriate requests to Django's built in administration functions.

Now edit the models.py file in the ledger application directory so that it contains the following:

from django.db import models
from django.contrib import admin

class Account(models.Model):
    account_name = models.CharField(max_length=40)
    account_type = models.CharField(max_length=40)
    account_number = models.CharField(max_length=40)
    account_slug = models.SlugField()
    starting_balance = models.DecimalField(max_digits=12, decimal_places=2)
    current_balance = models.DecimalField(max_digits=12, decimal_places=2)
    
class Transaction(models.Model):
    date = models.DateField()
    account = models.ForeignKey(Account)
    number = models.IntegerField()
    payee = models.CharField(max_length=40)
    amount = models.DecimalField(max_digits=12, decimal_places=2)

admin.site.register(Account)
admin.site.register(Transaction)

We have added an import statement allowing us to use the administration interface functions. At the end of the file we call the admin.site.register() function, which tells Django that we would like the registered models, in this case the Account and Transaction models, to be available in the administration interface.

These simple changes to the urls.py file and the models.py file are all that we need to do enable basic administration interface functionality.

Before demonstrating the administration interface we must first initialize and synchronize our database. Run the following command from the checkbook project directory:

python manage.py syncdb

You should see the following output:

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 ledger_account
Creating table ledger_transaction

You just installed Django's auth system, which means you don't have any superusers defined.
Would you like to create one now?

Answer yes at the prompt. Django will then prompt you for a username, email address, and password. Make sure to remember what you answer here. After answering all the prompts you should see the following output:

Superuser created successfully.
Installing index for auth.Message model
Installing index for auth.Permission model
Installing index for admin.LogEntry model
Installing index for ledger.Account model
Installing index for ledger.Transaction model

The syncdb command has created and initialized all of the database tables needed by our application. Because we are using applications provided by Django, like the authorization system and the administration interface, we see that the syncdb command has created tables to support these applications, in addition to the tables needed to reflect the data models in our ledger application.

At this point we can launch the development server:

python manage.py runserver :80

Open up your web browser, and navigate to the following URL:

http://localhost/admin

You should see the login page for the Django administration interface. Enter the username and password you created during the syncdb process. After logging in you will see the administration interface. Notice that there are sections for the Auth and Sites applications we included in are settings file. We will ignore these for now.

We will now use the administration interface to enter in some data. In the ledger section click on the add link on the Accounts line.

Not finished with this chapter yet...

Errors

During the process of developing a website with Django you will run into bugs in your code. Django provides an excellent debugging interface, which can make the process of hunting down and fixing bugs much easier. Errors and debugging information are displayed in the browser when a request triggers a bug.

To see this in action, launch your project using the runserver command and navigate to the following URL:

http://localhost/

Not finished with this chapter yet...