Archive for September, 2008

Modifying the default Django admin site

September 29, 2008

Those of you following my posts, anybody?, know that I am experimenting with creating my own little CMS using Django. I am starting by following the Django tutorials. I got through the first 1 and 3/4 tutorials before needing to do some research into how to get my recursive relationship key working in the admin site. It turns out I was a little naive regarding how the model system works in Django. Anyway, I finally got this sorted out and now I am back to continuing with the last part of the second tutorial.

The initial aim of my app is to be able to create multiple menu structures. So far I have two models, a menu model:

class Menu(models.Model):
    title = models.CharField(max_length=100)
    description = models.CharField(max_length=1000)
    in_use = models.BooleanField(default=True)

    def __unicode__(self):
        return self.title

    def is_in_use(self):
        return self.in_use == True

and a node model (which might turn into a “page” model in the future):

class Node(models.Model):
    title = models.CharField(max_length=100)
    sort_order = models.IntegerField(default=1)
    menu = models.ForeignKey('Menu')
    parent = models.ForeignKey('self', blank=True, null=True)
    is_visible = models.BooleanField(default=True)

    def __unicode__(self):
        return self.title

This has given me the basic admin site that you get from using django.contrib.admin. Customising the admin site is also straightforward. By adding a few lines to the Menu class in my admin.py file I added filtering, searching and the ability to generate multiple nodes whilst adding a menu.

For filtering:

list_filter = ['in_use']

For searching:

search_fields = ['title']

And for multiple nodes I created this class:

class NodeInline(admin.TabularInline):
    model = Node
    extra = 5

And then appended this line to my MenuAmdin class:

inlines = [NodeInline]

Finally I also modified the data brought back in my list of menus:

list_display = ('title', 'in_use')

I also copied the base_site.html file into my own templates directory and modified it to display my own title in the admin system. I will be playing around with the ability to customise the backend more in the future as this is one the things I really like about Django. It’s so easy to modify the backend.

So far I have written maybe 50 lines of “code” (well really it’s more like templating instructions) and have a fully functioning admin site. This is great, but also a little worrying as I am sure I will hit a major road block in the future with my limited Python knowledge. Still, so far so good.

Making database schema changes for Django

September 25, 2008

As I found out in my previous post it is not possible to modify your database schema via Django. This means once you have created the schema for your models, the only thing you can do is add more models using:

python manage.py syncdb

If you make a change to a model you are on your own.

When I defined my first model for a node (as in a node in a hierarchy) the model was something like this:

class Node(models.Model):
    title = models.CharField(max_length=100)
    sort_order = models.IntegerField(default=1)
    menu = models.ForeignKey('Menu')
    parent = models.ForeignKey('self')
    is_visible = models.BooleanField(default=True)

    def __unicode__(self):
        return self.title

The problem with this is that this model does not allow for a root node, since the parent column is a foreign key on itself, but the root node must by definition not have a parent (that is parent = NULL in the database). So I changed the model to this:

class Node(models.Model):
    title = models.CharField(max_length=100)
    sort_order = models.IntegerField(default=1)
    menu = models.ForeignKey('Menu')
    parent = models.ForeignKey('self', blank=True, null=True)
    is_visible = models.BooleanField(default=True)

    def __unicode__(self):
        return self.title

This allows the parent column to be null and is fine for the model, but now I need to make the change to the schema manually in sqlite.

So first I run this script to see what table schema the model is expecting:

python manage.py sql cwcms

Which brings back this:

CREATE TABLE "cwcms_node" (
    "id" integer NOT NULL PRIMARY KEY,
    "title" varchar(100) NOT NULL,
    "sort_order" integer NOT NULL,
    "menu_id" integer NOT NULL REFERENCES "cwcms_menu" ("id"),
    "parent_id" integer NULL,
    "is_visible" bool NOT NULL
)
;

I then need to log in to sqlite and make the change to the schema to reflect this. This is easily done with the manage.py script:

python manage.py dbshell

Next, because sqlite only supports basic ALTER TABLE syntax (ie you can’t change column definitions) you essentially need to create your new table schema using a different name. Then copy the contents from the old table into the new one, delete the old table and then rename the new table to the name of the old table (ie the one that Django is expecting). The series of statements would look something like this:

CREATE TABLE "cwcms_node_new" (
    "id" integer NOT NULL PRIMARY KEY,
    "title" varchar(100) NOT NULL,
    "sort_order" integer NOT NULL,
    "menu_id" integer NOT NULL REFERENCES "cwcms_menu" ("id"),
    "parent_id" integer NULL,
    "is_visible" bool NOT NULL
);

INSERT INTO cwcms_node_new SELECT * FROM cwcms_node;

DROP TABLE cwcms_node;

ALTER TABLE cwcms_node_new RENAME TO cwcms_node;

Another way of doing this if you’re still early on in development is to simply get the commands needed to clear the database using manage.py:

python manage.py sqlclear cwcms

And run them on the your sqlite database and use syncdb to recreate your whole database. In either case it is probably worth creating scripts to populate your database with some dummy default data for testing purposes.

And now, after around 6 hours work from the initial install of Django I have a fully functioning admin interface that allows me to create any number of menus with any kind of hierarchy.

Recursive relationships in Django

September 24, 2008

In my previous post I stated how I had created a recursive relationship in one mof my models. The code looks something like this:


parent = models.ForeignKey('self', blank=True, null=True)

This works great, but the problem I now faced was how to enter the root element. Even though I had sent blank and null to True I was not able to enter my root node using the default admin interface. So this is the first issue I need to solve outside of the tutorial documentation.

Ok, after some searching around it is clear that I need to update the database schema to reflect the model change. Obviously this won’t happen automatically. So I thought I could use the syncdb statement that was used to create the schema in the first place:


python manage.py syncdb

But, as it clearly says in the documentation, syncdb only creates new tables. It does not modify existing tables. The solution seems to be to run the following the statement:

python manage.py sql APP_TITLE

To see what Table schema your model is expecting and to then make the change manually in your database. Not ideal, but manageable. They are working on ways to evolve the schema via Django, but that will be for a future version I guess.

This is the first disapointing thing about Django I’ve encountered so far. The idea seems to be that you get your schema right the first time, but this seems be unrealistic to me. Especially in an agile development environment where you are likely to make small changes at a time.

My first Django app

September 15, 2008

Today I followed the first two pages of the Django tutorial with one small difference. Instead of recreating the polling app I decided to start right away on my Contented Web app (CW).

Creating the Django project and my app was straight forward – although I must admit I don’t understand the technical nuances of everything that I am following. I think partially this is due to the Unix environment I am using.

The models I created are essentialy enough for creating a hierarchical menu structure. This seemed straightforward and I quickly got the hang of how to define and alter my models, including a recursive relationship.

What blew me away though was the admin site that was created and how easily I could modify the look and feel of the forms through the use of python tuples and lists. Pure class. As far as I can tell the whole admin site can be remodelled without affecting the functionality. This is great. One of the annoying things about most CMS tools is that whilst they enable you to create great looking websites, the admin interface always seems to be forgotten. And there seems to be little thought given to making it customisable.

So after about 3 hourse of playing around with Django and following the first two tutorials I now have a fully functioning admin interface for my models, including user management and access rights.

Getting Django

September 12, 2008

So, my first post is about downloading and installing Django. Makes sense.

My development environment, by the way, is Ubuntu running in VMWare player on Windows Vista. I decided to go with Unix for my Django work as it seems that that is what most of the documentation is targeted at. I had tried Virtual PC 2007, but be warned, Ubuntu on Virtual PC 2007 does not work out of the box.

I followed the steps on the “How to install Django” page. Ubuntu already shipped with the latest version of Python so there was no need to install that.  I did not install Django on Apache as this a development environment and I will simply be using the inbuilt python webserver and I chose to go for SQLite as the database which also comes as part of Django – mainly so I could get going asap. Getting this far took me about an hour. Most of which was time spent getting to grips with Ubuntu.