Repository: zostera/django-bootstrap3

Issue:
Missing sub widgets classes in django.contrib.admin.widgets.AdminSplitDateTime widget rendering
adminAdminSplitDateTime is a MultiWidget consisting of an AdminDateWidget and a AdminTimeWidget. When rendered with django-bootstrap3 both are missing respectively a vDateField and vTimeField class so the django default javascript does not work.

Sort of a test case:

```
from django.contrib.admin.widgets import AdminSplitDateTime

class RendererTestCase(TestCase):
        def setUp(self):
            class TestForm(forms.Form):
                datetime = forms.DateTimeField(widget=AdminSplitDateTime())

            self.form = TestForm({
                'datetime': '20140111 10:00:00',
            })

        def test_render_datetime(self):
            field = self.form['datetime']
            html = renderers.FieldRender(field).render()
            #html = field.as_widget()
            self.assertIn('vDateField', html)
            self.assertIn('vTimeField', html)
```



Discussion:
So i've tracked down the issue to FieldRenderer.add_class_attrs: when it adds a class attribute, even if empty the rendering breaks. This workaround works for me:

```
     def add_class_attrs(self):
+        if isinstance(self.widget, MultiWidget):
+            return
```

Thinking a bit more about this a proper way to handle that could be in the MultiWidget case to call the usual machinery on all sub widgets instead of the MultiWidget itself. I'm giving this a try.


Resolving patch:
diff --git a/bootstrap3/renderers.py b/bootstrap3/renderers.py
--- a/bootstrap3/renderers.py
+++ b/bootstrap3/renderers.py
@@ -3,7 +3,7 @@
 from django.contrib.auth.forms import ReadOnlyPasswordHashWidget
 
 from django.forms import (
-    TextInput, DateInput, FileInput, CheckboxInput,
+    TextInput, DateInput, FileInput, CheckboxInput, MultiWidget,
     ClearableFileInput, Select, RadioSelect, CheckboxSelectMultiple
 )
 from django.forms.extras import SelectDateWidget
@@ -203,6 +203,7 @@ def __init__(self, field, *args, **kwargs):
         super(FieldRenderer, self).__init__(*args, **kwargs)
 
         self.widget = field.field.widget
+        self.multi_widget = isinstance(field.field.widget, MultiWidget)
         self.initial_attrs = self.widget.attrs.copy()
         self.field_help = text_value(mark_safe(field.help_text)) \
             if self.show_help and field.help_text else ''
@@ -251,44 +252,59 @@ def __init__(self, field, *args, **kwargs):
     def restore_widget_attrs(self):
         self.widget.attrs = self.initial_attrs
 
-    def add_class_attrs(self):
-        classes = self.widget.attrs.get('class', '')
-        if isinstance(self.widget, ReadOnlyPasswordHashWidget):
+    def add_class_attrs(self, widget=None):
+        if widget is None:
+            widget = self.widget
+        classes = widget.attrs.get('class', '')
+        if isinstance(widget, ReadOnlyPasswordHashWidget):
             classes = add_css_class(
                 classes, 'form-control-static', prepend=True)
-        elif not isinstance(self.widget, (CheckboxInput,
+        elif not isinstance(widget, (CheckboxInput,
                                           RadioSelect,
                                           CheckboxSelectMultiple,
                                           FileInput)):
             classes = add_css_class(classes, 'form-control', prepend=True)
             # For these widget types, add the size class here
             classes = add_css_class(classes, self.get_size_class())
-        self.widget.attrs['class'] = classes
-
-    def add_placeholder_attrs(self):
-        placeholder = self.widget.attrs.get('placeholder', self.placeholder)
-        if placeholder and is_widget_with_placeholder(self.widget):
-            self.widget.attrs['placeholder'] = placeholder
-
-    def add_help_attrs(self):
-        if not isinstance(self.widget, CheckboxInput):
-            self.widget.attrs['title'] = self.widget.attrs.get(
+        widget.attrs['class'] = classes
+
+    def add_placeholder_attrs(self, widget=None):
+        if widget is None:
+            widget = self.widget
+        placeholder = widget.attrs.get('placeholder', self.placeholder)
+        if placeholder and is_widget_with_placeholder(widget):
+            widget.attrs['placeholder'] = placeholder
+
+    def add_help_attrs(self, widget=None):
+        if widget is None:
+            widget = self.widget
+        if not isinstance(widget, CheckboxInput):
+            widget.attrs['title'] = widget.attrs.get(
                 'title', strip_tags(self.field_help))
 
-    def add_required_attrs(self):
-        if self.set_required and is_widget_required_attribute(self.widget):
-            self.widget.attrs['required'] = 'required'
+    def add_required_attrs(self, widget=None):
+        if widget is None:
+            widget = self.widget
+        if self.set_required and is_widget_required_attribute(widget):
+            widget.attrs['required'] = 'required'
 
-    def add_disabled_attrs(self):
+    def add_disabled_attrs(self, widget=None):
+        if widget is None:
+            widget = self.widget
         if self.set_disabled:
-            self.widget.attrs['disabled'] = 'disabled'
+            widget.attrs['disabled'] = 'disabled'
 
     def add_widget_attrs(self):
-        self.add_class_attrs()
-        self.add_placeholder_attrs()
-        self.add_help_attrs()
-        self.add_required_attrs()
-        self.add_disabled_attrs()
+        if self.multi_widget:
+            widgets = self.widget.widgets
+        else:
+            widgets = [self.widget]
+        for widget in widgets:
+            self.add_class_attrs(widget)
+            self.add_placeholder_attrs(widget)
+            self.add_help_attrs(widget)
+            self.add_required_attrs(widget)
+            self.add_disabled_attrs(widget)
 
     def list_to_class(self, html, klass):
         classes = add_css_class(klass, self.get_size_class())


Test patch:
diff --git a/bootstrap3/tests.py b/bootstrap3/tests.py
--- a/bootstrap3/tests.py
+++ b/bootstrap3/tests.py
@@ -8,6 +8,7 @@
 from django import forms
 from django.forms.formsets import formset_factory
 from django.template import Template, Context
+from django.contrib.admin.widgets import AdminSplitDateTime
 
 from .text import text_value, text_concat
 from .exceptions import BootstrapError
@@ -39,6 +40,7 @@ class TestForm(forms.Form):
     Form with a variety of widgets to test bootstrap3 rendering.
     """
     date = forms.DateField(required=False)
+    datetime = forms.DateTimeField(widget=AdminSplitDateTime(), required=False)
     subject = forms.CharField(
         max_length=100,
         help_text='my_help_text',
@@ -214,7 +216,12 @@ def test_field_names(self):
         form = TestForm()
         res = render_form(form)
         for field in form:
-            self.assertIn('name="%s"' % field.name, res)
+            # datetime has a multiwidget field widget
+            if field.name == "datetime":
+                self.assertIn('name="datetime_0"', res)
+                self.assertIn('name="datetime_1"', res)
+            else:
+                self.assertIn('name="%s"' % field.name, res)
 
     def test_field_addons(self):
         form = TestForm()
@@ -329,6 +336,11 @@ def _test_size_medium(param):
         _test_size_medium('medium')
         _test_size_medium('')
 
+    def test_datetime(self):
+        field = render_form_field('datetime')
+        self.assertIn('vDateField', field)
+        self.assertIn('vTimeField', field)
+
 
 class ComponentsTest(TestCase):
     def test_icon(self):