Technobabble

Showing only posts with tags: Django Atom feed

Hacking Django forms for CSS flexibility

The default output of the Django forms (former newforms) module is not very CSS friendly. With a few simple adjustments, you can make your web designer colleague happy.

This patch will add three classes on the parent HTML element of the rendering of each form field (the tr, li or p tag depending on your rendering mode):

  1. The type of the form field. (Examples: CharField, ModelChoiceField)
  2. The type of the widget. (Examples: TextInput, SelectInput)
  3. Is the form field optional or required: Optional or Required
  4. Now a required DateField will render, using the as_table rendering, as:
1  <tr class="DateField TextInput Required">
2    <th>
3      <label for="id_date">Date</label>
4    </th>
5    <td>
6      <input type="text" name="date" id="id_date" />
7    </td>
8  </tr>

Example uses

A couple of example use cases where my patch will help you out:

  • Special styling of required fields possible.
  • Easier to add a date picker by JavaScript.
  • Special styling of checkboxes (styling input elements to width: 100% also affects those).

Download the patch

Patch against forms/forms.py in Django 1.0: Download - View

How to patch your newly downloaded Django-1.0.tar.gz

For those of you not quite familiar with working with patches:

$ wget http://www.hacktheplanet.dk/export/HEAD/misc/forms.py.patch
$ wget http://www.djangoproject.com/download/1.0/tarball/
$ tar xvfz Django-1.0.tar.gz
$ patch -d Django-1.0/django/forms/ < forms.py.patch

Django and mod_wsgi: A perfect match!

mod_wsgi is an Apache module for serving WSGI-based Python web applications from the Apache HTTP server. Django, along with almost every other Python web framework today, comes bundled with a backend for acting like a WSGI application.

A couple of months ago I decided to try it out in spite of mod_python. Discovering and trying out mod_wsgi really suprised me. It can take a massive beating, and outperforms mod_python in every practical aspect.

The setup

You will need a short Python "bootstrap" script to create a WSGI-handler for your Django project. Here is an example (call it wsgi_handler.py and place it in the root directory of your Django project - the one with manage.py and settings.py):

import sys
import os

sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/..')
os.environ['DJANGO_SETTINGS_MODULE'] = 'projectname.settings'

import django.core.handlers.wsgi

application = django.core.handlers.wsgi.WSGIHandler()

Finally set up your Apache virtualhost to use mod_wsgi:

<VirtualHost *>

  ServerName www.projectname.org
  ServerAlias *projectname.org

  Alias /admin_media /usr/lib/python2.4/site-packages/django/contrib/admin/media

  <Location /admin_media>
    Order allow,deny
    Allow from all
  </Location>

  Alias /media /home/user/projectname/media

  <Location /media>
    Order allow,deny
    Allow from all
  </Location>

  WSGIScriptAlias / /home/user/projectname/wsgi_handler.py

  WSGIDaemonProcess projectname user=user group=user processes=1 threads=10
  WSGIProcessGroup projectname

</VirtualHost>

In the WSGIDaemonProcess line, you can easily manage the amount of system resources (measured in processes and threads) mod_wsgi should use. In my experience a single process with 10 threads will cover most small to medium loaded websites.

Why?

This is some of the reasons why you should ditch mod_python for mod_wsgi when hosting Django projects:

Faster

The load times of the websites now served with mod_wsgi really surprised me. Normally a page would be served within 150-300 ms. This was reduced to load times in the range of 40-80 ms.

I also discovered that running mod_wsgi in embedded mode (as opposed to daemon mode) was not worth the effort. I didn't really see any difference between load times when using Django.

Less memory usage

Everyone hosting more than a couple of Django projects on a single Apache instance knows that Django projects squanders a bit with memory usage, and every single Apache child process will easily end up using 50 MB RAM.

mod_wsgi dedicates a process (or multiple processes) to a single interpreter for a single Django project, and keeps the memory usage low in the "normal" Apache child processes. On a server with 8 small Django projects, I went from using ~1500 MB RAM on Apache child processes to using 150 MB.

Secure

When using mod_python your Python interpreter will be running as the user running the Apache webserver itself (on Debian systems, the user is called www-data). Typically this will allow you to peek around in places where you do not want your users peeking. This is due to the fact that www-data must have read access to every file you use in your application (including settings/configuration/media files).

mod_wsgi addresses this problem by changing to a user id specified in the configuration file, and run your Python interpreter as another user than www-data, allowing you to lock down every project on your server to seperate user accounts.

These points cover mod_wsgi running in daemon mode.

Conclusion

mod_wsgi rocks!

So if you are thinking about moving your systems to, or just curious about, mod_wsgi, you should really get to it. I, for one, welcome our new mod_wsgi overlords! (sorry)

Futher reading

About Django and the importance of releases

My favorite Python web framework, Django, has not been updated for a long time. The most current release, 0.96, was released in March 2007. This is a very long time, when you're in the market of web frameworks.

This doesn't seem to bother a lot of people, as the common answer in the django community seems to be just to run the trunk version (development version).

I for one doesn't like that solution. And here are some of the reasons why.

Some of the problems with running a development version

  • When a security release is made, I cannot just update, but need to merge the change in, in all of my installations. An update could maybe break my existing code with backward incompatible changes.

  • It's easier to tell my co-workers that our projects will run 0.96, and not r6389 for one project and r7291 for another (+ a couple of security patches). That's okay if you are a single-person team working on a single project, but not when you have several people and projects.

  • Developers are afraid to commit new things to trunk, because a lot of users will be disappointed when they eagerly update their repositories each morning just to find that backwards compatibility has been broken. A good example of this is ticket 3639:

    This patch will be committed to trunk eventually, don't worry. But I, personally,
    haven't done it yet because of the massive backwards-incompatibility it introduces,
    making timing important.
    

No-one should ever be afraid to break backwards compatibility in the development version. This will just add more complexity to the job of making new releases by queuing up a lot of uncommitted patches.

Release early, release often

Many great people have preached this paradigm a lot. And for a reason. This is the best way for an open source project to succed. If you don't know what your users want, you will most likely fail.

Getting out releases will foster users to tell you about their experiences and if you are heading in the right direction with development with regards to what the users actually need.

Having more frequent releases would probably also spur more developers to contribute. Developers like active projects and getting their contributions released.

A suggestion

One of my biggest problems with the current (0.96) release is how buggy newforms is. This could easily be solved by making django.newforms (and maybe django.contrib.admin) into a separate project with a separate release schedule.

Then everyone wouldn't have to rewrite a lot of form handling code when the 1.0 release happens, but would now currently be using cleaned_data and ModelForms in spite of their current incarnations.

Conclusion

Despite my points in this post, I still think The Django framework is the best thing that has happened to the craft of building web applications since the invention of HTTP ;-)

XML-RPC dispatching through Django test client

Django has a cleverly designed test client that creates a WSGIRequest and routes it through your views without the need of a web server. Furthermore it works on a temporary test database, thus any errors won't have any effect on your live database.

I'm currently working on an application written using Django. This application, amongst other features, has an XML-RPC interface. The tests for this interface was being done by spawning a local web server and carrying out the tests over the wire. However, it would be nice to exploit the Django test framework.

The solution is amazingly simple. I wrote a TestTransport class for xmlrpclib:

import cStringIO

from xmlrpclib import Transport

from django.test.client import Client

class TestTransport(Transport):

    """ Handles connections to XML-RPC server through Django test client."""

    def __init__(self, *args, **kwargs):

        self.client = Client()

    def request(self, host, handler, request_body, verbose=0):

        self.verbose = verbose

        response = self.client.post(handler,
                                    request_body,
                                    content_type="text/xml")

        res = cStringIO.StringIO(response.content)
        res.seek(0)

        return self.parse_response(res)

In my tests where I use the XML-RPC interface I initialize the ServerProxy object with:

server = ServerProxy("http://localhost/xmlrpc/", transport=TestTransport())

That's about it. Now calls trough the server object will be handled by the Django test client.

In my case I had a decorator around the view function handling XML-RPC requests checking the HTTP headers for authentication information and replying with a 401 error. I had to modify this to allow the test client access with no authentication (you may not need this, but I will show it as an example on how to know in your view that this is a test client request). Here is the relevant parts cut out:

def xmlrpc_authenticate(func):
...
    def inner(request, *args, **kwargs):
...
        if request.META.get('SERVER_NAME', '') == 'testserver':
            return func(request, *args, **kwargs)
...
    return inner