Opened 11 years ago

Closed 11 years ago

Last modified 10 years ago

#21173 closed Bug (fixed)

Django DateTimeInput determines language/locale at startup time but this may change later, resulting in validation errors

Reported by: django-locale-issue@… Owned by: Claude Paroz
Component: Forms Version: dev
Severity: Normal Keywords: forms, widgets, locale
Cc: Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

My application works in two locales, 'nl' and 'en'. In some situations, when editing content, Django formats datetimes in the wrong format which won't validate later.

The problem, I suspect is in the fact that DateTimeInput determines/stores the locale format in its init, which means at startup/import time in most cases. It cannot handle a changing locale later.

This is with Django 1.4.x but the relevant code doesn't appear to be any different on 1.5.x

I've tested/verified the issue in an interactive django shell session:

# In my settings I have LANGUAGE_CODE = 'nl' 
# but the user can switch to english later.
# Forms, fields and widgets are usually initialized at startup/import time
# DateTimeField users DateTimeInput which uses the current language to determine the preferred format

In [1]: from django.contrib.auth.forms import UserChangeForm

In [2]: print UserChangeForm().fields['last_login'].widget.format
%d-%m-%Y %H:%M:%S

# this is a rather dutch format
# imagine the user switches to english
In [3]: from django.utils import translation

In [4]: translation.activate('en')

In [5]: print UserChangeForm().fields['last_login'].widget.format
%d-%m-%Y %H:%M:%S

# the format remains unchanged, which isn't good but not fatal. Let's format a string using this
In [6]: from datetime import datetime

In [7]: now = datetime.now()

In [8]: print UserChangeForm().fields['last_login'].widget._format_value(now)
26-09-2013 15:33:13

# now feed this string back to the field, as if the form was filled in and then submitted without change
In [11]: print UserChangeForm().fields['last_login'].to_python("26-09-2013 15:33:13")
---------------------------------------------------------------------------
ValidationError                           Traceback (most recent call last)
/home/ivo/.buildout/eggs/Django-1.4.5-py2.7.egg/django/core/management/commands/shell.pyc in <module>()
----> 1 print UserChangeForm().fields['last_login'].to_python("26-09-2013 15:33:13")

/home/ivo/.buildout/eggs/Django-1.4.5-py2.7.egg/django/forms/fields.pyc in to_python(self, value)
    435                 return None
    436             value = '%s %s' % tuple(value)
--> 437         result = super(DateTimeField, self).to_python(value)
    438         return from_current_timezone(result)
    439 

/home/ivo/.buildout/eggs/Django-1.4.5-py2.7.egg/django/forms/fields.pyc in to_python(self, value)
    354                         except ValueError:
    355                             continue
--> 356         raise ValidationError(self.error_messages['invalid'])
    357 
    358     def strptime(self, value, format):

ValidationError: [u'Enter a valid date/time.']

# oops!

Change History (9)

comment:1 by Daniele Procida, 11 years ago

Triage Stage: UnreviewedAccepted

comment:2 by Simon Charette, 11 years ago

AFAIK determining locale at the form initialization phase shouldn't cause any issues because LocaleMiddleware already activated the current language.

How are you activating user specific language in your application?

comment:3 by django-locale-issue@…, 11 years ago

Form initialization kicks in at startup / import time, LocaleMiddleWare doesn't happen until the first request.
LocaleMiddleware may activate a different language than the widget was initialized with.

comment:4 by Claude Paroz, 11 years ago

I think the basic issue is that widget.is_localized is False by default, and contrib.auth forms doesn't set it. In your own forms, you can probably activate is_localized and the self.format variable set in the __init__ method will not be used.

Personally, I'd vote for setting is_localized to the settings.USE_L10N value by default. But this might be backwards incompatible.

A more contained fix would be to let self.format to None when it is not specified in the widget constructor, and then getting the format only if and when it is needed in _format_value.

comment:5 by Claude Paroz, 11 years ago

Owner: changed from nobody to Claude Paroz
Status: newassigned
Version: 1.4master

comment:6 by Claude Paroz, 11 years ago

Has patch: set

Pull request: https://github.com/django/django/pull/1720
Isn't less code better :-)

comment:7 by Claude Paroz <claude@…>, 11 years ago

Resolution: fixed
Status: assignedclosed

In 00a73c1c699e3df2790caf56635647bd72e3cd21:

Fixed #21173 -- Stopped fixing format for date-based widgets at init time

Thanks Marc Tamlyn for the review.

comment:8 by Markus Bertheau, 10 years ago

Any chance this makes it into 1.6? It's a pretty serious issue where the user accessing the page with a different language preference breaks date(time) fields in all forms.

comment:9 by Markus Bertheau, 10 years ago

I noticed that now date and time widgets ignore is_localized and always localize. I like this, but I haven't seen it discussed, so I wondered whether this was an oversight. Previous behaviour was to use the first format for the LANGUAGE_CODE in settings.py as a default format that'll change only when overridden manually or when is_localized is True.

I think it makes sense this way around. If you need a certain format instead of a localized one, it should be controlled by passing format to the widget, not by settings.LANGUAGE_CODE.

Note: See TracTickets for help on using tickets.
Back to Top