2

I have a localized Django App, localisation works well and the configuration is ok…

For forms needs, I use __unicode__ method for model to render ModelChoiceFields, but how do I format localized date in the unicode return?

In this method I have no access to current timezone, how to display my TimeSpanList correctly to my users? Currently, it displays UTC. I tryed django.template.defaultfilters.date and Simon Charette's django.utils.formats.localize which didn't helped as they probably lacked contextual data…

class TimeSpan(models.Model):
    start = models.DateTimeField(_("start"))
    end = models.DateTimeField(_("end"))

    def __unicode__(self):
        return u"from UTC:{0} to UTC:{1}".format(self.start, self.end)

class TimeSpanChooserForm(forms.Form):
    time_span = forms.ModelChoiceField(
        label=_("time span"), queryset=TimeSpan.objects.all())

    def __init__(self, *args, **kwargs):
        self.request = kwargs.pop("request", None)
        super(TimeSpanChooserForm, self).__init__(*args, **kwargs)

How to know current locale without current request object to localize those datetimes? (If there is a way)

note: to me __unicode__ seems like the only way to display entries in ModelChoiceField.

note 2: to me, Yuji 'Tomita' Tomita comment is the best current answer but it lacks a usable exemple…

christophe31
  • 6,359
  • 4
  • 34
  • 46
  • You can't. Don't use the unicode method in the template, access the dates directly. – Daniel Roseman Mar 27 '15 at 16:19
  • But for a choice field handled by a form, how do you do then? – christophe31 Mar 27 '15 at 16:47
  • You can always pass the request to the form. – Brandon Taylor Mar 27 '15 at 17:22
  • 3
    You shouldn't think about modifying `__unicode__` strictly to determine what gets populated in a ChoiceField by default. These are handy automatic defaults. Beyond that, I would modify the form directly. Converting to the client locale seems something very much local to a view and therefore the request. Otherwise, the admin representation of these TimeSpans would default to your own admin TZ. Look into modifying a choice fields choices dynamically! – Yuji 'Tomita' Tomita Mar 27 '15 at 18:46
  • I added a form base so you can complete it in a full answer that I would be able accept, how would you change the render with localized dates from here? – christophe31 Mar 28 '15 at 07:50
  • What does this have to do with time zones? Is it mistagged? Or was there some question with regard to that? – Matt Johnson-Pint Apr 20 '15 at 15:06
  • The problem here is displaying the datetime in form rendering according to the user timezone when you have no access to request objects and you must return unicode instead of lazy loading tranlation string… – christophe31 Apr 21 '15 at 12:07
  • Are you asking how you to display datetimes in a user specific timezone? – Simon Charette Apr 21 '15 at 22:26
  • yes, in the specific context of a choice field… – christophe31 Apr 22 '15 at 09:05
  • Along with @Yuji'Tomita'Tomita approach, wouldn't it be enough to populate the `choices` for your `time_span` field dynamically in the `__init__()` method as outlined [in this SO answer](http://stackoverflow.com/a/2959366/870769)? – sthzg Apr 23 '15 at 14:40
  • @sthzg thanks for bringing this post back to my attention. Agreed. These two components are all you need, but the actual implementation ended up a bit more involved.. busy work. This would all be easy if we did this with JS, where the client side timezone comes from anyways. – Yuji 'Tomita' Tomita Apr 24 '15 at 05:03

3 Answers3

4

Thanks for reminding me about this question. It's a bit involved for a complete answer.

I've created a demo method that shows this in action. Run test() to see output.

The remaining challenge is getting the client timezone. This will probably be done with some JavaScript that adds a parameter to your GET/POST or adds a custom header to be read in the view.

Since this is an unknown, I just stubbed it out with a method that returns a random time zone. You should update it to reflect your client timezone retrieval method.

Here's a self documenting example:

Models

from django.utils.formats import localize


class TimeSpan(models.Model):
    start = models.DateTimeField("start")
    end = models.DateTimeField("end")

    def __unicode__(self):
        return u"from UTC:{0} to UTC:{1}".format(self.start, self.end)

Form

from django import forms

class TimeSpanChooserForm(forms.Form):
    time_span = forms.ModelChoiceField(
        label=("time span"), queryset=TimeSpan.objects.all())

    def __init__(self, *args, **kwargs):
        # get request and TZ from view. This is a special form..
        self.request = kwargs.pop("request", None)
        self.tz = kwargs.pop('tz', None)
        super(TimeSpanChooserForm, self).__init__(*args, **kwargs)

        # modify the choices attribute to add custom labels.
        self.fields['time_span'].choices = [
            self.make_tz_aware_choice(self.request, timespan) for timespan in self.fields['time_span'].queryset
        ]

    def make_tz_aware_choice(self, request, timespan):
        """ Generate a TZ aware choice tuple.
        Must return (value, label). In the case of a ModelChoiceField, (instance id, label).
        """
        start = timespan.start.replace(tzinfo=self.tz)
        end = timespan.end.replace(tzinfo=self.tz)
        label = "From {tz} {start} to: {tz} {end}".format(
            start=start,
            end=end,
            tz=self.tz,
        )
        return (timespan.id, label)

View

import random
import pytz

from django import http
from django import template

def view(request):
    """ Render a form with overridden choices respecting TZ
    """
    def get_tz_from_request(request):
        """ Get the client timezone from request.
        How you do this is up to you. Likely from JS passed as a parmeter.
        """
        random_timezone = random.choice(pytz.all_timezones)
        return pytz.timezone(random_timezone)

    form = TimeSpanChooserForm(request.POST or None, request=request, tz=get_tz_from_request(request))
    ctx = template.Context({
        'form': form,
    })
    rendered_template = template.Template("{{ form }}").render(ctx)
    return http.HttpResponse(rendered_template)


def test():
    from django.test import RequestFactory

    rf = RequestFactory()
    r = rf.get('/')

    for i in range(10):
        print str(view(r))
Yuji 'Tomita' Tomita
  • 115,817
  • 29
  • 282
  • 245
0

You can use the django.utils.formats.localize function.

from django.db import models
from django.utils.formats import localize
from django.utils.translation import ugettext

class TimeSpan(models.Model):
    start = models.DateTimeField(_('start'))
    end = models.DateTimeField(_('end'))

    def __unicode__(self):
        return ugettext("from %(start)s to %(end)s") % {
            'start': localize(self.start),
            'end': localize(self.end),
        }

You can test the following work with those manipulation.

from django.utils import timezone
from django.utils.translation import override

with override('fr'):
    print(TimeSpan(start=timezone.now(), end=timezone.now()))

with override('en'):
    print(TimeSpan(start=timezone.now(), end=timezone.now()))

Both should display different formating.

If you want to make sure dates are displayed in a specific timezone you must make sure USE_TZ = True in your settings and set the TIME_ZONE setting to the one you want to use. You can set this timezone per request by using a middleware that calls django.utils.timezone.activate with the desired timezone.

You can also use the django-sundial package which takes care of this for you.

Simon Charette
  • 5,009
  • 1
  • 25
  • 33
  • How does the localize fonction now the good locale? I just tryed, it still returns UTC datetime… – christophe31 Apr 21 '15 at 12:24
  • If you want Django to take the browser's locale preference into account you have to make sure `LocaleMiddleware` is in your `MIDDLEWARE_CLASSES` setting, that both `USE_I18N` and `USE_L10N` settings are set to `True` and that you have entries for language you want to support in your `LANGUAGE` setting. – Simon Charette Apr 21 '15 at 22:18
  • my settings are ok, all dates displayed by django template or in datetime fields (django forms) are good… – christophe31 Apr 22 '15 at 09:07
-2

Try this:

def __unicode__(self):
    return u'%s %s'%(self.start,self.end)

Buy using this it will return combined start and end datetime objects.This worked for me

Zealous System
  • 2,080
  • 11
  • 22
  • My problem is about localizing these datetime… I suppose the answer is more complex, I started by what you suggest but it displays UTC datetime instead of localized numbers… – christophe31 Apr 21 '15 at 12:26