Mar 29, 2012

Timezones. New in Django 1.4.

What and Why?

Under Django version 1.4 all dates stored and displayed for a single timezone. The one that is specified in project.settings.TIME_ZONE. Now you gain ability to store dates in UTC and render it with timezone correction.

Problems with localtime bypass is an additional plus. They can happen once a year. For e.g. 31 of November 2012 in Russia. Time from 2:00am to 3:00am in fact goes by twice. It may not be a problem for 99% of users. But it can become a nightmare for billing systems. So it's better to store time in UTC and display with user Time Zone correction. So "02:15am 31 November 2012" will become "2012-10-30T22:15:00+04:00" and "2010-10-30T23:15:00+03:00" that is so handy for programmers ;).

Concepts

datetime objects in Python support timezones with attribute tzinfo. If this attribute is filled out it is called "timezone-aware", otherwise it's "naive" date.
Django uses timezone-aware dates, if you've used new feature in Django 1.4 while update. (settings.USE_TZ=True)
"Default timezone" in Django is the one that is specified in settings.TIME_ZONE. (E.g. 'Europe/Kiev'). While "current timezone" is the one that activated this moment.

Current timezone

You need to know where is user from to show him his date and time. You can ask him directly and store his timezone in session BTW. You can use DB's like "maxmind", guessing his timezone by IP or even use JavaScript: Date.getTimezoneOffset(). One or another way we need to activate user's timezone to get access to rendering of timezone aware date and time in templates and forms.

Example of setting current timezone (from Django docs):

from django.utils import timezone

class TimezoneMiddleware(object):
    def process_request(self, request):
        tz = request.session.get('django_timezone')
        if tz:
            timezone.activate(tz)


Main functionality we need for this issue is concentrated in 'django.utils.timezone'.

Templates

Those dates workout is widely supported in templates along with Python code.

# Converting to Paris timezone
{% timezone "Europe/Paris" %}
    Paris time: {{ value }}
{% endtimezone %}

# default date display, like they're actually stored (UTC)
{% localtime off %}
    {{ value }}
{% endlocaltime %}


Migration

  1. Setting settings.USE_TZ = True 
  2. pip install pytz (read farther)
  3. Convert DB datetime values into UTC time. (If it is different) 


Now your code supports timezones. You have to repair some old places where Django does not handle it for us. We have to convert naive dates into timezone-aware. Otherwise you can get wired bugs while changing Winter/Somer time. To complete the migration you have to use 'django.utils.timezone' and debug places where dates in new format are compared to "new" one's.

pytz

Library that handles handy timezone workout in python. Olson database and handy API are included for different timezone calculations. Despite it is optional in Django 1.4 developers recommend to include it. It helps Framework not to guess default timezones while calculating time and provides timezone list to render for users.

Conclusion

Those new features make me proud user of this framework and simplify many holes that I had to handle myself. E.g. with hosting app using Australia/Sydney Timezone and working with it in Europe/Kiev. ;)

Mar 28, 2012

Django template tags to find out field type


Sometimes you have the task to decorate django form fields. In our case it was need to add different widget to form field depending on field type.
You can know your field type by refering to it's python class name, e.g.:
field.__class__.__name__


You will get wrong symbols error if you try to refer this python variable in templates directly. So you need a custom template tag to serve here. Decision was not to return variable for tag but to add context variable, cause I had need to perform exact actions on certain field types.

So tag example:
@register.simple_tag(takes_context=True)
def set_this_field_type(context, field):
    """
    Adds to context given field type variable
    variable named "this_field_type"
    """
    context["this_field_type"] = field.field.__class__.__name__
    return ''
Now you can add this variable to your if statements. E.g. usage:
{% load my_template_lib %}
{% for field in form %}
    {# Setting this field type in context #}
    {% set_this_field_type field %}
{% if not this_field_type = "DateField" %}
    {# Rendering specific for Date fields (With DatePicker widget) #}
    <div class="field-wrapper {{ this_field_type }}"
        <label class="control-label">{{ field.label }}</label>
        <input id="{{ field.id_for_label }}" class="datepicker-widget" type="text" name="{{ field.name }}">
    </div>
{% else %}
    {# Default field rendering #}
    <div class="field-wrapper {{ this_field_type }}"
         <label class="control-label">{{ field.label }}</label>
        {{ field }}
    </div>
{% endif %}
{% endfor %}
You will get your form fields rendered in custom manner. You will have the CSS class with field type for styling or you can use this variable in context to render various widgets for e.g.
Hope you've got the main concept here. Now this idea must be limited only by your imagination.

Including those "simple" approaches kindly suggested by guys at 'reddit.com' They are more simple and can play in your case maybe ;)
Using the filter: (Thanks to POTUS @reddit.com)

@register.filter()
def field_type(field):
    return field.field.__class__.__name__

{% if not field|field_type ="DateField" %}
    <span>Whatever</span>
{%endif%}


Using the widget: (Thanks to tgerdes @reddit.com)

from django import forms
class DateForm(forms.Form):
    date = forms.DateField(
      widget=forms.DateInput(attrs= {'class': 'datepicker-widget' } ))

form = DateForm()
print form.as_p()

outputs: <p><label for="id_date">Date:</label> <input id="id_date" type="text" class="datepicker-widget" name="date" /></p>

However this approach in not this topics main cause it concludes we only have to style input. But in some cases it may be quite enough...

Thanks guys for correction!

Please drop me a comment here if you've liked/used this decision(s).