Tuesday, June 19, 2012

Django verbose_name in your template

There are many situations when you want to display model field verbose_name in template when displaying tables and other data. Ideally you would put this value in header section of your table for DRY purposes.
The problem is that it's unable to use model _meta in templates. You can achive the same using an example verbose_name_tags.py templatetag.
# verbose_name_tags.py

from django import template

register = template.Library()

def get_field_verbose_name(instance, arg):
    return instance._meta.get_field(arg).verbose_name
register.filter('field_verbose_name', get_field_verbose_name)

def get_queryset_field_verbose_name(queryset, arg):
    return queryset.model._meta.get_field(arg).verbose_name
register.filter('queryset_field_verbose_name', get_queryset_field_verbose_name)

you can get your header names rendered the DRY way.
Assuming your model looks like this:
class Product(models.Model):
    name = models.CharField(_(u"Name"), max_length=20)
    weight = models.PositiveIntegerField(_(u"Weight"))

Your template would be similar to the one below:
{% load verbose_name_tags %}

<table>
  <thead>
  <tr>    
    <th>{{ object_list|queryset_field_verbose_name:"name" }}</th>
    <th>{{ object_list|queryset_field_verbose_name:"weight" }}</th>
  </tr>
  </thead>
  <tbody>
{% for product in object_list %}    
  <tr>
    <td>{{ product.name }}</td>
    <td>{{ product.weight }}</td>
  </tr>
{% endfor %}
</table>

What if you need sorting ?
For some time I've been using a django-sorting for sorting my tabular data.
The new code
{% load sorting_tags %}
{% autosort object_list %}

<table>
  <thead>
  <tr>    
    {# _("Name").. need to specify the display name all the time.. bad.. #}
    <th>{% autosort 'name' _("Name") %}</th>
    <th>{% autosort 'weight' _("Weight") %}</th>
  </th></tr>
  </thead>
  <tbody>
{% for product in object_list %}  
  <tr>
    <td>{{ product.name }}</td>
    <td>{{ product.weight }}</td>
  </tr>
{% endfor %}
  </tbody>
</table>

Great, but this is not DRY! You can do this the other way. Note: I think {% autosort %} should take verbose_name by default (maybe will take a look into the code later, but for now we can achieve the same using a {% with %} and verbose_name_tags.py template tags.
{% load sorting_tags %}
{% load verbose_name_tags %}

{% autosort object_list %}

{% endfor %}
<table>
  <thead>
  <tr>
    {% with name=object_list|queryset_field_verbose_name:"name" %}    
    <th>{% autosort 'name' name%}</th>
    {% endwith %}
    {% with weight=object_list|queryset_field_verbose_name:"weight" %}
    <th>{% autosort 'weight' weight %}</th>
    {% endwith %}
  </tr>
  </thead>
  <tbody>
  <tr>
    <td>{{ product.name }}</td>
    <td>{{ product.weight }}</td>
  </tr>
  </tbody>
</table>

Now we can just change verbose_name in models.py and we don't have to worry about the table headers.
You can also use a "field_verbose_name" for model instances:
{# assuming {{ product }} is an instance of Product model #}
{% load verbose_name_tags %}

{{ product|field_verbose_name:"name" }}: {{ product.name }}

Some might wonder why didn't I simply use one templatetag name which would identify if it checks for queryset or instance field. I decided to use different names to quickly see If I act with querysets or instances. Hope you like it.

Thursday, June 14, 2012

Twitter Bootstrap button radius (rounded corners) size

Twitter Bootstrap is a great tool from Twitter. It's a CSS framework (using lesscss) that lets you create your own CSS stylesheets easily.

In one of projects I needed to decrease the size of buttons radius, which is too huge by default (~5px).

Thanks to .border-radius() mixin you can change button radius.

Here's the code I put in my own .less file.
You can put this in bootstrap.less (where necessary imports are included) file and recompile.

The file below changes radius to 1px.

@new_radius: 2px;

.dropdown-menu {
        .border-radius(0px);
}
.btn {
        .border-radius(@new_radius);
}
.btn-group > .btn {
  .border-radius(@new_radius);
}
.btn-group > .btn:first-child {
        .border-radius(@new_radius 0 0 @new_radius);
}
.btn-group > .btn:last-child,
.btn-group > .dropdown-toggle {
        .border-radius(0 @new_radius @new_radius 0);
}
.btn-group > .btn.large:first-child {
        .border-radius(@new_radius 0 0 @new_radius);
}
.btn-group > .btn.large:last-child,
.btn-group > .large.dropdown-toggle {
        .border-radius(0 @new_radius @new_radius 0);                                                                         
}
.btn-group > .dropdown-toggle:first-child {
        .border-radius(@new_radius);
}
.btn-group > .btn-large:only-child,
.btn-group > .btn:only-child {
        .border-radius(@new_radius);
}

Hope you find it useful!

Wednesday, April 11, 2012

Clearly and IReader - Safari Reader in your Chrome and Firefox

If you are a Safari user and use the "reader" function you will find such functionality missing in Google Chrome / Firefox.

IReader @ Chrome

Safari Reader is a nice builtin function that makes reading long articles easy. It removes banners so the article looks more like reading a plain book.
There are 2 extensions I found nice.

1. IReader (Chrome / Firefox )
2. Clearly (Chrome / Firefox )


Clearly @ Chrome
Try both. Haven't read the code, but seems like IReader parses the HTML code on site load what makes site loading slower (it's just my assumption). When IReader is available for the page, new icon appears in omnibox.
Clearly icon is always visible in the extensions box.

Yeah.. I'm a strong chrome extensions opponent.. it makes Chrome slooower.





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!

Thursday, February 9, 2012

Google Chrome slow? Disable extensions and get the real speed

I'm a Google Chrome fan since early versions. With every new version it consumed more memory. Today, after upgrading it to v. 17 I have decided to try Firefox v.10 What I saw was faster site rendering! Though FF seems to be less responsive (especially when switching tabs) sites were loading faster. It also  consumed less memory.

Finaly, I have disabled all chrome extensions with --disable-extensions switch. Viva Chrome! It's fast again!

If your chrome gets slower, disable extensions. You won't get fast page loads with extensions enabled.

Here's the extension list I was using:

  • Last Pass, 
  • Page Rank, 
  • Gmail Checker+, 
  • Firebug Lite, 
  • Smooth Gestures, 
  • RSS Finder, 
  • FlashBlock (that's the most suspicious one regarding the page load imho).
Question is "Is Google Chrome worth using without some of these extensions?" ...

Edit: You can also speed up Chrome with RAMDisk .
In my Chrome shortcut I added flags as follows:

--disk-cache-size=26214400  --media-cache-size=26214400 --disk-cache-dir=g:/ --purge-memory-button

This limits cache size to ~26 Megabytes.





Wednesday, February 1, 2012

Django language set in the database field applied on login

In one of my Django projects available only for logged in users I needed to set language setting in the database, so user can change the language setting in his profile.

Here comes a handy "user_logged_in" (Django >= 1.3) signal which lets us do things after user is logged in. In our case, we modify session and change the language.

My user_details.models.py:
from django.dispatch import receiver
from django.contrib.auth.signals import user_logged_in
from django.utils.translation import ugettext_lazy as _
from django.db import models
from django.conf import settings

class UserProfile(models.Model):
    user = models.OneToOneField(User, blank=Flse)
    language = models.CharField(verbose_name=_(u"Interface Language"), 
                   max_length=4, choices=settings.LANGUAGES)

@receiver(user_logged_in)
def lang(sender, **kwargs):
    lang_code = kwargs['user'].userprofile.language
    kwargs['request'].session['django_language'] = lang_code


settings.py

gettext = lambda s: s

LANGUAGES = (
    ('pl',gettext('Polski')),
    ('en',gettext('English')),
    ('de',gettext('Deutsch')),
)