Skip to content

Commit

Permalink
Add support for input type color (#46)
Browse files Browse the repository at this point in the history
* Add support for input type color
* Prepare for textarea
* Update documentation
  • Loading branch information
dyve committed Apr 18, 2021
1 parent 3beaa84 commit 49057cb
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 39 deletions.
12 changes: 9 additions & 3 deletions docs/forms.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,14 @@ Floating labels

Reference: https://getbootstrap.com/docs/5.0/forms/floating-labels/

This behavior can be triggere dby setting `layout="floating"`.
This behavior can be triggered by setting `layout="floating"`.

Floating labels are supported for widgets that can use `form-control`. An exception is `FileInput` and its descendants, those labels cannot be floating.
Setting `layout="floating"` has no effect on widgets that are not supported.

Floating labels are supported for widgets that can use `form-control`, with the exception of widgets that have special markup:

- `FileInput` and its descendants
- `TextInput` with type `color`

The `Select` widget is supported by Bootstrap 5 (with restrictions), but not yet supported by this package.

Setting `layout="floating"` has no effect on widgets that are not supported.
6 changes: 3 additions & 3 deletions example/app/forms.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
from django import forms
from django.contrib.admin.widgets import AdminSplitDateTime
from django.forms import BaseFormSet, formset_factory
from django.forms import BaseFormSet, TextInput, formset_factory

from django_bootstrap5.widgets import RadioSelectButtonGroup

RADIO_CHOICES = (("1", "Radio 1"), ("2", "Radio 2"))


MEDIA_CHOICES = (
("Audio", (("vinyl", "Vinyl"), ("cd", "CD"))),
("Video", (("vhs", "VHS Tape"), ("dvd", "DVD"))),
Expand All @@ -27,7 +26,7 @@ class TestForm(forms.Form):
)
xss_field = forms.CharField(label='XSS" onmouseover="alert(\'Hello, XSS\')" foo="', max_length=100)
password = forms.CharField(widget=forms.PasswordInput)
message = forms.CharField(required=False, help_text="<i>my_help_text</i>")
message = forms.CharField(required=False, help_text="<i>my_help_text</i>", widget=forms.Textarea)
sender = forms.EmailField(label="Sender © unicode", help_text='E.g., "me@example.com"')
secret = forms.CharField(initial=42, widget=forms.HiddenInput)
cc_myself = forms.BooleanField(
Expand All @@ -46,6 +45,7 @@ class TestForm(forms.Form):
choices=MEDIA_CHOICES, widget=forms.CheckboxSelectMultiple, help_text="Check as many as you like."
)
category5 = forms.ChoiceField(widget=RadioSelectButtonGroup, choices=MEDIA_CHOICES)
color = forms.CharField(widget=TextInput(attrs={"type": "color"}))
addon = forms.CharField(widget=forms.TextInput(attrs={"addon_before": "before", "addon_after": "after"}))

required_css_class = "django_bootstrap5-req"
Expand Down
61 changes: 28 additions & 33 deletions src/django_bootstrap5/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,11 @@
CheckboxInput,
CheckboxSelectMultiple,
ClearableFileInput,
DateInput,
EmailInput,
FileInput,
MultiWidget,
NumberInput,
PasswordInput,
RadioSelect,
Select,
Textarea,
TextInput,
TimeInput,
URLInput,
)
from django.forms.widgets import FileInput, Input
from django.utils.html import conditional_escape, format_html, strip_tags
from django.utils.safestring import mark_safe

Expand Down Expand Up @@ -211,19 +203,6 @@ def render(self):
class FieldRenderer(BaseRenderer):
"""Default field renderer."""

FORM_CONTROL_WIDGETS = (
TextInput,
NumberInput,
EmailInput,
URLInput,
DateInput,
TimeInput,
Textarea,
PasswordInput,
FileInput,
)
FLOATING_WIDGETS = (TextInput, NumberInput, EmailInput, URLInput, DateInput, TimeInput, Textarea, PasswordInput)

def __init__(self, field, *args, **kwargs):
if not isinstance(field, BoundField):
raise BootstrapError('Parameter "field" should contain a valid Django BoundField.')
Expand Down Expand Up @@ -290,39 +269,55 @@ def restore_widget_attrs(self):

def is_widget_form_control(self, widget):
"""Return whether given widget is of type `form-control`."""
return isinstance(widget, self.FORM_CONTROL_WIDGETS)
return isinstance(widget, Input) and not isinstance(widget, CheckboxInput)

def get_widget_input_type(self, widget):
"""Return input type of widget, or None."""
return widget.input_type if isinstance(widget, Input) else None

def can_widget_float(self, widget):
"""Return whether given widget can be set to `form-floating` behavior."""
return isinstance(widget, self.FLOATING_WIDGETS)
# TODO: Add support for select widgets, within Bootstrap 5 restrictions
# TODO: Add support for textarea widgets
# TODO: Check support for date, time and other types
return (
not isinstance(widget, FileInput)
and self.is_widget_form_control(widget)
and self.get_widget_input_type(widget) != "color"
)

def add_widget_class_attrs(self, widget=None):
"""Add class attribute to widget."""
if widget is None:
widget = self.widget
size_prefix = None
classes = widget.attrs.get("class", "")

before = []
classes = [widget.attrs.get("class", "")]
if ReadOnlyPasswordHashWidget is not None and isinstance(widget, ReadOnlyPasswordHashWidget):
classes = merge_css_classes("form-control-static", classes)
before.append("form-control-static")
elif self.is_widget_form_control(widget):
classes = merge_css_classes("form-control", classes)
before.append("form-control")
if widget.input_type == "color":
before.append("form-control-color")
size_prefix = "form-control"
elif isinstance(widget, Select):
classes = merge_css_classes("form-select", classes)
before.append("form-select")
size_prefix = "form-select"
elif isinstance(widget, CheckboxInput):
classes = merge_css_classes("form-check-input", classes)
before.append("form-check-input")

if size_prefix:
classes = merge_css_classes(classes, get_size_class(self.size, prefix=size_prefix, skip=["xs", "md"]))
classes.append(get_size_class(self.size, prefix=size_prefix, skip=["xs", "md"]))

if self.field.errors:
if self.error_css_class:
classes = merge_css_classes(classes, self.error_css_class)
classes.append(self.error_css_class)
elif self.field.form.is_bound:
classes = merge_css_classes(classes, self.success_css_class)
classes.append(self.success_css_class)

widget.attrs["class"] = classes
classes = before + classes
widget.attrs["class"] = merge_css_classes(*classes)

def add_placeholder_attrs(self, widget=None):
"""Add placeholder attribute to widget."""
Expand Down
20 changes: 20 additions & 0 deletions tests/test_bootstrap_field.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django import forms
from django.core.files.uploadedfile import SimpleUploadedFile
from django.forms import TextInput
from django.test import TestCase

from django_bootstrap5.exceptions import BootstrapError
Expand Down Expand Up @@ -33,6 +34,25 @@ class TestForm(forms.Form):
),
)

def test_bootstrap_field_color(self):
"""Test field with text widget."""

class TestForm(forms.Form):
test = forms.CharField(widget=TextInput(attrs={"type": "color"}))

test_form = TestForm()
html = render_template_with_bootstrap("{% bootstrap_field form.test %}", context={"form": test_form})
self.assertHTMLEqual(
html,
(
'<div class="django_bootstrap5-req mb-3">'
'<label for="id_test" class="form-label">Test</label>'
'<input class="form-control form-control-color" '
'id="id_test" name="test" placeholder="Test" required type="color">'
"</div>"
),
)

def test_bootstrap_field_checkbox(self):
"""Test field with text widget."""

Expand Down

0 comments on commit 49057cb

Please sign in to comment.