Search code examples
djangodatetimedjango-modelstimezonedjango-i18n

Django models __unicode__: How to return a value containing localized datetimes?


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…


Solution

  • 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))