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!