Repository: django-oscar/django-oscar

Issue:
datetime picker format is not "clean" - cannot add new voucher in dashboard
I can't add a voucher in the dashboard
something wrong with the datetime format

attached screendump

oscar version 0.7.2

in my settings.py
LANGUAGES = (('da', gettext_noop('Danish')),)
LANGUAGE_CODE = 'da'
TIME_ZONE = 'Europe/Copenhagen' 



Discussion:
@michaelhjulskov, could you please provide a full stack trace and the screen dump you mention?

Hi Maik

I cant show you a traceback, as it is simply a validationerror

I just testet a bit and i have found the same error when setting exp date
in offer, so its a global error here
My guess is that it is the date picker that's inserting a wrong format
when i click a date in the js date picker, its inserting date in this
format "14.08.2014 .. 00:00" in the field

I tried removing the two dots manually and form was validated without
errors.
so its the two dot's that shouldnt be there.

if you still want to see the image, you can see it here
https://groups.google.com/forum/#!searchin/django-oscar/voucher/django-oscar/auIJpzj-3Qg/Rc9lxmifP2UJ

hope it helps

Michael

2014-08-08 12:44 GMT+02:00 Maik Hoepfel notifications@github.com:

> @michaelhjulskov https://github.com/michaelhjulskov, could you please
> provide a full stack trace and the screen dump you mention?
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/tangentlabs/django-oscar/issues/1440#issuecomment-51587046
> .


Resolving patch:
diff --git a/oscar/apps/dashboard/offers/forms.py b/oscar/apps/dashboard/offers/forms.py
--- a/oscar/apps/dashboard/offers/forms.py
+++ b/oscar/apps/dashboard/offers/forms.py
@@ -29,8 +29,7 @@ class RestrictionsForm(forms.ModelForm):
     def __init__(self, *args, **kwargs):
         super(RestrictionsForm, self).__init__(*args, **kwargs)
         today = datetime.date.today()
-        self.fields['start_datetime'].initial = today.strftime(
-            self.fields['start_datetime'].widget.format)
+        self.fields['start_datetime'].initial = today
 
     class Meta:
         model = ConditionalOffer
diff --git a/oscar/forms/widgets.py b/oscar/forms/widgets.py
--- a/oscar/forms/widgets.py
+++ b/oscar/forms/widgets.py
@@ -9,6 +9,7 @@
 from django.forms.widgets import FileInput
 from django.template import Context
 from django.template.loader import render_to_string
+from django.utils import formats
 from django.utils.encoding import force_text
 from django.utils.html import format_html
 from django.utils.safestring import mark_safe
@@ -61,76 +62,164 @@ def __init__(self, *args, **kwargs):
 
 def datetime_format_to_js_date_format(format):
     """
-    Convert a Python datetime format to a date format suitable for use with JS
-    date pickers
+    Convert a Python datetime format to a date format suitable for use with
+    the JS date picker we use.
     """
+    format = format.split()[0]
+    return datetime_format_to_js_datetime_format(format)
+
+
+def datetime_format_to_js_time_format(format):
+    """
+    Convert a Python datetime format to a time format suitable for use with the
+    JS time picker we use.
+    """
+    try:
+        format = format.split()[1]
+    except IndexError:
+        pass
     converted = format
     replacements = {
-        '%Y': 'yy',
-        '%m': 'mm',
-        '%d': 'dd',
-        '%H:%M': '',
+        '%H': 'hh',
+        '%I': 'HH',
+        '%M': 'ii',
+        '%S': 'ss',
     }
     for search, replace in six.iteritems(replacements):
         converted = converted.replace(search, replace)
     return converted.strip()
 
 
-def datetime_format_to_js_time_format(format):
+def datetime_format_to_js_datetime_format(format):
     """
-    Convert a Python datetime format to a time format suitable for use with JS
-    date pickers
+    Convert a Python datetime format to a time format suitable for use with
+    the datetime picker we use, http://www.malot.fr/bootstrap-datetimepicker/.
     """
     converted = format
     replacements = {
-        '%Y': '',
-        '%m': '',
-        '%d': '',
-        '%H': 'HH',
-        '%M': 'mm',
+        '%Y': 'yyyy',
+        '%y': 'yy',
+        '%m': 'mm',
+        '%d': 'dd',
+        '%H': 'hh',
+        '%I': 'HH',
+        '%M': 'ii',
+        '%S': 'ss',
     }
     for search, replace in six.iteritems(replacements):
         converted = converted.replace(search, replace)
 
-    converted = re.sub('[-/][^%]', '', converted)
-
     return converted.strip()
 
 
-def add_js_formats(widget):
+class TimePickerInput(forms.TimeInput):
     """
-    Set data attributes for date and time format on a widget
+    A widget that passes the date format to the JS date picker in a data
+    attribute.
     """
-    attrs = {
-        'data-dateFormat': datetime_format_to_js_date_format(
-            widget.format),
-        'data-timeFormat': datetime_format_to_js_time_format(
-            widget.format)
-    }
-    widget.attrs.update(attrs)
+    def render(self, name, value, attrs=None):
+        format = self.format
+        if hasattr(self, 'manual_format'):
+            # For django <= 1.6.5, see https://code.djangoproject.com/ticket/21173
+            if self.is_localized and not self.manual_format:
+                format = force_text(formats.get_format('DATE_INPUT_FORMATS')[0])
+        else:
+            # For django >= 1.7
+            format = format or formats.get_format(self.format_key)[0]
+
+        input = super(TimePickerInput, self).render(name, value, attrs)
+
+        attrs = {'data-oscarWidget': 'time',
+                 'data-timeFormat':
+                 datetime_format_to_js_time_format(format),
+                 }
+
+        div = format_html('<div class="input-append date"{}>', flatatt(attrs))
+        return mark_safe('{div}'
+                         ' {input}'
+                         ' <span class="add-on">'
+                         '  <i class="icon-time"></i>'
+                         ' </span>'
+                         '</div>'
+                         .format(div=div, input=input))
 
 
 class DatePickerInput(forms.DateInput):
     """
-    DatePicker input that uses the jQuery UI datepicker.  Data attributes are
-    used to pass the date format to the JS
+    A widget that passes the date format to the JS date picker in a data
+    attribute.
     """
-    def __init__(self, *args, **kwargs):
-        super(DatePickerInput, self).__init__(*args, **kwargs)
-        add_js_formats(self)
+    def render(self, name, value, attrs=None):
+        format = self.format
+        if hasattr(self, 'manual_format'):
+            # For django <= 1.6.5, see https://code.djangoproject.com/ticket/21173
+            if self.is_localized and not self.manual_format:
+                format = force_text(formats.get_format('DATE_INPUT_FORMATS')[0])
+        else:
+            # For django >= 1.7
+            format = format or formats.get_format(self.format_key)[0]
+
+        input = super(DatePickerInput, self).render(name, value, attrs)
+
+        attrs = {'data-oscarWidget': 'date',
+                 'data-dateFormat':
+                 datetime_format_to_js_date_format(format),
+                 }
+
+        div = format_html('<div class="input-append date"{}>', flatatt(attrs))
+        return mark_safe('{div}'
+                         ' {input}'
+                         ' <span class="add-on">'
+                         '  <i class="icon-calendar"></i>'
+                         ' </span>'
+                         '</div>'
+                         .format(div=div, input=input))
 
 
 class DateTimePickerInput(forms.DateTimeInput):
-    # Build a widget which uses the locale datetime format but without seconds.
-    # We also use data attributes to pass these formats to the JS datepicker.
+    """
+    A widget that passes the datetime format to the JS datetime picker in a
+    data attribute.
 
+    It also removes seconds by default. However this only works with widgets
+    without localize=True.
+
+    For localized widgets refer to
+    https://docs.djangoproject.com/en/1.6/topics/i18n/formatting/#creating-custom-format-files # noqa
+    instead to override the format.
+    """
     def __init__(self, *args, **kwargs):
         include_seconds = kwargs.pop('include_seconds', False)
         super(DateTimePickerInput, self).__init__(*args, **kwargs)
 
-        if not include_seconds:
+        if not include_seconds and self.format:
             self.format = re.sub(':?%S', '', self.format)
-        add_js_formats(self)
+
+    def render(self, name, value, attrs=None):
+        format = self.format
+        if hasattr(self, 'manual_format'):
+            # For django <= 1.6.5, see https://code.djangoproject.com/ticket/21173
+            if self.is_localized and not self.manual_format:
+                format = force_text(formats.get_format('DATETIME_INPUT_FORMATS')[0])
+        else:
+            # For django >= 1.7
+            format = format or formats.get_format(self.format_key)[0]
+
+        input = super(DateTimePickerInput, self).render(name, value, attrs)
+
+        attrs = {'data-oscarWidget': 'datetime',
+                 'data-datetimeFormat':
+                 datetime_format_to_js_datetime_format(format),
+                 }
+
+        div = format_html('<div class="input-append date"{}>', flatatt(attrs))
+        return mark_safe('{div}'
+                         ' {input}'
+                         ' <span class="add-on">'
+                         '  <i class="icon-calendar"></i>'
+                         ' </span>'
+                         '</div>'
+                         .format(div=div, input=input))
 
 
 class AdvancedSelect(forms.Select):


Test patch:
diff --git a/tests/unit/forms/widget_tests.py b/tests/unit/forms/widget_tests.py
--- a/tests/unit/forms/widget_tests.py
+++ b/tests/unit/forms/widget_tests.py
@@ -24,9 +24,8 @@ def compare_time_format(format, expected):
 
 def test_datetime_to_time_format_conversion():
     format_testcases = (
-        ('%Y-%m-%d', ''),
         ('%Y-%m-%d %H:%M', 'HH:mm'),
-        ('%d/%m/%Y', ''),
+        ('%H:%M', 'HH:mm'),
     )
     for format, expected in format_testcases:
         yield compare_time_format, format, expected