Opened 17 months ago

Closed 17 months ago

Last modified 7 months 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: claudep
Component: Forms Version: master
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 Changed 17 months ago by EvilDMP

  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset
  • Triage Stage changed from Unreviewed to Accepted

comment:2 Changed 17 months ago by charettes

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 Changed 17 months ago by django-locale-issue@…

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 Changed 17 months ago by claudep

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 Changed 17 months ago by claudep

  • Owner changed from nobody to claudep
  • Status changed from new to assigned
  • Version changed from 1.4 to master

comment:6 Changed 17 months ago by claudep

  • Has patch set

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

comment:7 Changed 17 months ago by Claude Paroz <claude@…>

  • Resolution set to fixed
  • Status changed from assigned to closed

In 00a73c1c699e3df2790caf56635647bd72e3cd21:

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

Thanks Marc Tamlyn for the review.

comment:8 Changed 7 months ago by mbertheau

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 Changed 7 months ago by mbertheau

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