Tuesday, March 27, 2012

Gunicorn + Nginx - a much better way to deploy your Django website

I've been a lighttpd + FastCGI django user for a long time.
Now there's a better solution -> gunicorn.

What is a gunicorn?
Gunicorn 'Green Unicorn' is a Python WSGI HTTP Server for UNIX. It's a pre-fork worker model ported from Ruby's Unicorn project. The Gunicorn server is broadly compatible with various web frameworks, simply implemented, light on server resources, and fairly speedy.
If you are a fastcgi user, please check gunicorn asap. I'm sure you will get much more from your hardware :-)
Ps: Note that you get the most if database is not the bottleneck. That's why you should use cache everywhere if possible.

Here's my script for gunicorn + daemontools for supervising:
#!/bin/sh

# SITE_PATH is a directory where settings.py is stored
SITE_PATH=/var/www/django/projects/my_project/; export SITE_PATH
USER=www-data
TCP=127.0.0.1:8888

exec setuidgid $USER /usr/bin/gunicorn_django -b ${TCP} -w 3 --max-requests=1000 ${SITE_PATH}settings.py 2>&1

Seems like wsgi is also recommended with the new 1.4 version of Django
If you’re new to deploying Django and/or Python, we’d recommend you try mod_wsgi first. In most cases it’ll be the easiest, fastest, and most stable deployment choice.
Check django site for more information.

UPDATE:
Forgot to mention that I've switched from lighttpd to Nginx.
You can use the script above with the following nginx config:
server {
    listen   xx.xx.xx.xx:80;
    server_name domain.com www.domain.com;
    root /var/www/django/projects/my_project/public;
    access_log /var/log/nginx/domain-access.log;

    rewrite ^/admin(.*) https://domain.com/admin$1 permanent;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /media {
        root /var/www/django/projects/my_project/django/contrib/admin;
    }
    location /static {
        root /var/www/django/projects/my_project/site_media;
        access_log off;
    }
    location /site_media {
        root /var/www/django/projects/my_project;
    }
    location / {
        proxy_pass_header Server;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_set_header X-Forwarded-For  $remote_addr;
        proxy_set_header X-Scheme $scheme;
        proxy_connect_timeout 10;
        proxy_read_timeout 10;
        proxy_pass http://127.0.0.1:8888/;
    }

}

There's a trick you need to apply to get the real ip of the request source as seen in the line:
proxy_set_header X-Forwarded-For  $remote_addr;

Here's a code I'm using:
class XForwardedForMiddleware(object):
       def process_request(self, request):
        if "HTTP_X_FORWARDED_FOR" in request.META:
            ip = request.META["HTTP_X_FORWARDED_FOR"]
            if ip.startswith('::ffff:'):
                ip = ip[len('::ffff:'):]
            request.META["REMOTE_ADDR"] = ip
            request.META["REMOTE_HOST"] = None

Add XForwardedForMiddleware to your settings.py MIDDLEWARE_CLASSES setting.

Friday, March 2, 2012

Django HTML5 date input widget with i18n support and jQueryUI DatePicker fallback

Here's my manual for creating a HTML5 form date input with jQueryUI Datepicker and multilingual support.

What we need is:
1. django-floppyforms
2. jQueryUI Datepicker + jQuery 3. Modernizr javascript library
and of course - Django
You need to have some basic python, django & jquery knowledge.
Make sure multilingual support is enabled:
USE_I18N = True

and request context available for template
#settings.py

TEMPLATE_CONTEXT_PROCESSORS = (
  # ...
  'django.core.context_processors.request',
  # ...
)
In your base template, you can use LANGUAGE_CODE variable.
<html lang="{{ LANGUAGE_CODE }}">

There are 3 ways to get this running

1. Totally django way - prividing django form & widget with the "lang" variable"

Your calendar widget needs to gain information about the language, so you need to pass a variable to your form, which then sends this variable to the widget.
def add_view(request):
    
    form = SomeForm(data = request.POST, lang=request.LANGUAGE_CODE)
    if form.is_valid():
        do_some_stuff()

from django import forms
from common.widgets import DatePicker

class SomeForm(forms.ModelForm):
    data = forms.CharField()
    when = forms.DateField()

    def __init__(self, *args, **kwargs):
        lang = kwargs.pop('lang')
        super(SomeForm, self).__init__(*args, **kwargs)
        
        self.fields['when'].widget = DatePicker(lang=lang)
import floppyforms as forms
from django.conf import settings

class DatePicker(forms.DateInput):
    template = "common/datepicker.html"

    def __init__(self, *args, **kwargs):
        self.lang = kwargs.pop('lang')
        super(DatePicker, self).__init__(*args, **kwargs)
    
    def get_context(self, name, value, attrs):
        ctx = super(DatePicker, self).get_context(name, value, attrs)
        ctx['lang'] = self.lang
        ctx['STATIC_URL'] = settings.STATIC_URL
        return ctx
The below, could be attached to python render() method in the widget, but I think this is more elegant way, and thanks to floppyforms we get native HTML5 date widget if available. I am aware of that floppyforms has an example of html5 datepicker, but the way the floppyforms author checks for date type doesn't seem to work - that's why I've used modernizr.
{# "common/datepicker.html" #}
{% include "floppyforms/input.html" %}

<script type="text/javascript">
    if (!Modernizr.inputtypes.date) {
       $(function () {
         $.datepicker.setDefaults($.datepicker.regional['{{ lang }}']);
         $('#{{ attrs.id }}').datepicker({showOn: "both", buttonImageOnly: true, dateFormat: "yy-mm-dd", buttonImage: "{{ STATIC_URL }}new/img/calendar.gif"})} )}
</script>

What the above code does is check if
<input type="date" />
is available. If not, it runs the jqueryUI calendar widget.
The javascript attached is to the specified selector. I've decided to go this way as I needed the calendar widget on ajax loaded content.
This is a Django (well.. not the sortest way).

2. Javascript way. Less django form messing.

You can achieve the similar effect without prividing both SomeForm & DatePicker widget with the lang variable.
import floppyforms as forms

class DatePicker(forms.DateInput):
    template = "common/datepicker.html"
Let the form know the widget it should use
from django import forms
from common.widgets import DatePicker

class SomeForm(models.Model):
    data = forms.CharField()
    when = forms.DateField(widget=DatePicker)
In the widget template code, you need jQuery for language detection.
{# "common/datepicker.html" #}
{% include "floppyforms/input.html" %}

<script type="text/javascript">
    if (!Modernizr.inputtypes.date) {
       
         $(function () {
         var lang = $('html').attr('lang');
         $.datepicker.setDefaults($.datepicker.regional[lang]);
         $('#{{ attrs.id }}').datepicker({showOn: "both", buttonImageOnly: true, dateFormat: "yy-mm-dd", buttonImage: "{{ STATIC_URL }}new/img/calendar.gif"})} )}
</script>
but I'm affraid this might not work on ajax loaded content where you need your calendar widget.

3. Django way, without messing the widget code too much.

Another way could be simply providing the form with data-lang attribute. The form would then look as follows:
from django import forms
from common.widgets import DatePicker

class SomeForm(forms.ModelForm):
    data = forms.CharField()
    when = forms.DateField()

    def __init__(self, *args, **kwargs)
        lang = kwargs.pop('lang')
        super(SomeForm, self).__init__(*args, **kwargs)

        self.fields['when'].widget = DatePicker(attrs={'data-lang': lang})
and then in the datepicker.html
{# "common/datepicker.html" #}
{% include "floppyforms/input.html" %}

<script type="text/javascript">
    if (!Modernizr.inputtypes.date) {
       $(function () {
         $.datepicker.setDefaults($.datepicker.regional[$('#{{ attrs.id }}').attr('data-lang'));
         $('#{{ attrs.id }}').datepicker({showOn: "both", buttonImageOnly: true, dateFormat: "yy-mm-dd", buttonImage: "{{ STATIC_URL }}new/img/calendar.gif"})} )}
</script>

The 2 ways above have no {{ STATIC_URL }} in widget template available. You need to get it yourself, via templatetag, or simply using hardcoded url.

Hope someone gets some tips from this manual

There for sure are many other ways, feel free to share them. Remember to include javascript modernizr, jQuery and jQuery files!