Showing posts with label Django. Show all posts
Showing posts with label Django. Show all posts

Tuesday, July 10, 2007

Build a website in...

Rails started the craze of the 'build a blog in 15 minutes' example and since then there have been many 'me-too' screencasts, tutorials and blog posts.

This is not one of those super-exaggerated demos. This is an actual real-world example of 'build an X in Y minutes':

UntoldGood

Timed, from start to finish, this website took 2.5 hours.

2.5 hours for:

  • A simple CMS (set page name, and textile content)

  • A simple Links application (set link URL, text, and category)

  • HTML templates

  • CSS

  • Deployment


This isn't the most flashy website, but it is exactly to Seth's design spec (he actually came over with legal paper and cut outs and post-its taped to it). It is simple but this is all he needs. It isn't complete. The blog is missing, though we could fake it with the CMS. We talked about a lot of features he wanted, but he gladly accepted my rejection of all but the essential functions for the first cut.

Still, Seth was really happy. His ideas for the site started a mere 12 hours earlier, he called and asked 'what are you doing this afternoon?' a few hours earlier, and here he was with his website up on the Internet. He was making links to his friends' websites when he doesn't know a lick of HTML. He was already honing in on the next set of work for version 2 now that he got his hands on version 1.

I was really happy. I opened a beer and watched Seth fill in the links and tweak the copy through the admin interface. "This is really easy" he actually said out loud. I drank another beer and watched some TV. There were a couple of small tweaks that I made during commercials.

This one was under budget, and more importantly done. "Is that really when you came over?"

3 hours is a magic number in consulting. Nothing happens in less than 3 hours. Even on the easiest projects.

You forget about the overhead of starting anew, or you get sidetracked on a meaningless but tricky and fun challenge programming wise, or you just can't locate those certain docs, or track down that weird javascript bug, or can't remember how to do vertical centering, or scratch your head over an indentation bug, or can't decypher what this old code is actually doing (dang I should rewrite this), etc.

Then ADD kicks in. Reddit calls. I need a drink. New email.

Then damn apache is just not honoring my virtual host directives. Then WTF mod_php breaks mod_python and I can't log into the admin?! Then what is that Postgres password again?

I could go on.

But this one wasn't like that at all.

Conclusions to draw from all of this?

  1. Django kicks ass; apps are highly reusable and the admin interface provides an amazing shortcut for CRUD, and the clients love it.

  2. Sitting down next to the client to work is both a great motivator and and easy way to prioritize (what can we do while you are here?).

  3. Getting your tools, your workflow, and your deployment server as frictionless as possible is incredibly important -- you will know when this has happened and it will feel great.

Friday, June 29, 2007

Django MySQL with InnoDB Tables

If you are using MySQL and want Django to create InnoDB tables on `syncdb`, use the following in your settings file:

DATABASE_ENGINE = 'mysql'
DATABASE_OPTIONS = {"init_command": "SET storage_engine=INNODB"}


This works on trunk, and isn't documented very well.

Thursday, June 28, 2007

Django MEDIA_ROOT Deployment Recipe

One problem I have always had with Django is handling different paths between my and other developers development environments and the production environment.

For development purposes, exploiting the fact that settings.py is Python code leads to an adequate solution:

import os
MEDIA_ROOT = '%s/uploads/' % os.getcwd()


So when you do `./manage.py runserver` from the project directory, this variable will reflect any local environment. Unfortunately, if you deploy this app under Apache / mod_python, os.getcwd() does not refer to the project directory like runserver does.

It is trivial to replace the dynamic string with the absolute path to the project on the deployment server, but occasionally I get burned by Subversion either overwriting or finding a conflict with the modified file.

So one day when editing the Apache conf to set up another Django project with mod_python, I had an insight. Now my settings.py looks like this:

import os, posix
PROJECT_ROOT = posix.environ.get('PROJECT_ROOT') or os.getcwd()
MEDIA_ROOT = '%s/uploads/' % PROJECT_ROOT


This works exactly the same as the former solution with runserver. Then for apache in the virtual host configuration, I do this:

<VirtualHost *>
DocumentRoot /home/project/project

ServerName project.com
ServerAdmin noah@project.com

<Directory "/home/project/project">
SetHandler mod_python
PythonHandler django.core.handlers.modpython
PythonPath "['/home/project'] + sys.path"
SetEnv DJANGO_SETTINGS_MODULE project.settings
SetEnv PROJECT_ROOT "/home/project/project"
</Directory>

LogLevel warn
CustomLog /home/project/log/access.log combined
ErrorLog /home/project/log/error.log
</VirtualHost>


This line does the magic:

SetEnv PROJECT_ROOT "/home/project/project"


This moves the deployment server-specific configuration outside of my Django project and into Apache.

Django Template Block Extraction

I have been building a CMS for my job, in order to empower the Editor to manage all of the web page content. A simple TextField for Textile formatted content was sufficient for the www site, which is only text information. But for the support site, the need to mix managed content with dynamic elements (especially forms) arose quickly.

So how to mix the two?

After discussing implementations with the Editor, we found this syntax agreeable:

h1. Contact Us

p. You can use this form to send us a message.

b(#form).

p. Thank you for you interest.


The magic here is the fake, Textile-like `b` tag, with an ID. This indicates to insert some sort of dynamic element in between the normal Textile content.

Why `b`? On the backend, I realized that I wanted to use the full power of Django templates for any of these dynamic elements. How do you segment off sections of content within a Django template? Blocks.

Now the trick is to render the template as normal, render the `form` block, then replace `b(#form).` with the rendered block.

Here is the view logic:

def render(request, section, template, context_vars={}):
"""
Renders a section with a template. Substitues any
textile-ish `b.` blocks with orphan blocks found in
said template.
"""

# resolve the template and render it to a string
content = render_to_string(template, context_vars)

# add a recursive flatten method to NodeList to make
# getting blocks easy
from django.template import NodeList

def flatten(self):
nodes = []
for node in self:
nodes.append(node)
if hasattr(node, 'nodelist'):
nodes.extend(node.nodelist.flatten())
return nodes

NodeList.flatten = flatten

# load the template
from django.template.loader import get_template
t = get_template(template)

# get all the block nodes, by flattening NodeList
from django.template.loader_tags import BlockNode
nodes = t.nodelist.flatten()

blocknodes = []
for node in nodes:
if node.__class__ is BlockNode:
blocknodes.append(node)

# render blocknodes with context into
# name => content dict
from django.template.context import Context
context = Context(context_vars)

blocks = {}
for blocknode in blocknodes:
blocks[blocknode.name] = blocknode.render(context)

# parse out textile-style block tags b(#block-name)
# and substitute rendered block
p = re.compile(r'(b\(#([^\)]+)\).)')
matches = p.findall(content)
for match in matches:
tag, name = match
try:
content = content.replace(tag, blocks[name])
except:
pass
return HttpResponse(content)


And here is the corresponding template:

{% extends 'layout/one-column.html' %}

{% comment %}
This node is an orphan, and would not be rendered
with a normal render_to_string. It is, however,
possible to render it with the same context, then
replace part of render_to_string and return the
resulting string.
{% endcomment %}

{% block form %}
<form action="" method="post">
<table>
{{ form }}
<tr><td colspan="2" align="right">
<input type="submit" name="submit" value="Submit" />
</td></tr>
</table>
</form>
{% endblock %}