Skip to content

Commit

Permalink
Implement FileInput and ClearableFileInput
Browse files Browse the repository at this point in the history
  • Loading branch information
dyve committed Mar 20, 2021
1 parent f30dad4 commit c8197ee
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 21 deletions.
2 changes: 2 additions & 0 deletions example/app/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ class FilesForm(forms.Form):
file5 = forms.ImageField()
file4 = forms.FileField(required=False, widget=forms.ClearableFileInput)

use_required_attribute = False


class ArticleForm(forms.Form):
title = forms.CharField()
Expand Down
2 changes: 1 addition & 1 deletion example/app/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from django.views.generic import FormView
from django.views.generic.base import TemplateView

from app.forms import ContactForm, ContactFormSet, FilesForm
from .forms import ContactForm, ContactFormSet, FilesForm


# http://yuji.wordpress.com/2013/01/30/django-form-field-in-initial-data-requires-a-fieldfile-instance/
Expand Down
5 changes: 3 additions & 2 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ django==3.1.7
black==20.8b1
isort==5.7.0
flake8==3.9.0
pydocstyle==5.1.1
pydocstyle==6.0.0
docformatter==1.4
coverage==5.5
autoflake==1.4
tox==3.23.0
pur==5.4.0
wheel==0.36.2
setuptools==54.1.2
twine==3.3.0
twine==3.4.1
pillow==8.1.2
21 changes: 7 additions & 14 deletions src/django_bootstrap5/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
BoundField,
CheckboxInput,
CheckboxSelectMultiple,
ClearableFileInput,
DateInput,
EmailInput,
FileInput,
Expand Down Expand Up @@ -206,10 +207,6 @@ def _render(self):
class FieldRenderer(BaseRenderer):
"""Default field renderer."""

# These widgets will not be wrapped in a form-control class
WIDGETS_FORM_CONTROL = (TextInput, NumberInput, EmailInput, URLInput, DateInput, TimeInput, Textarea, PasswordInput)
WIDGETS_NO_FORM_CONTROL = (CheckboxInput, RadioSelect, CheckboxSelectMultiple, FileInput)

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 @@ -277,18 +274,18 @@ def add_class_attrs(self, widget=None):
size_prefix = None
classes = widget.attrs.get("class", "")
if ReadOnlyPasswordHashWidget is not None and isinstance(widget, ReadOnlyPasswordHashWidget):
# Render this is a static control
classes = merge_css_classes("form-control-static", classes)
elif isinstance(widget, self.WIDGETS_FORM_CONTROL):
elif isinstance(
widget,
(TextInput, NumberInput, EmailInput, URLInput, DateInput, TimeInput, Textarea, PasswordInput, FileInput),
):
classes = merge_css_classes("form-control", classes)
size_prefix = "form-control"
elif isinstance(widget, Select):
classes = merge_css_classes("form-select", classes)
size_prefix = "form-select"
elif isinstance(widget, CheckboxInput):
classes = merge_css_classes("form-check-input", classes)
elif isinstance(widget, FileInput):
classes = merge_css_classes("form-control-file", classes)
if size_prefix:
classes = merge_css_classes(classes, self.get_size_class(prefix=size_prefix))

Expand Down Expand Up @@ -319,6 +316,8 @@ def add_widget_attrs(self):
self.add_placeholder_attrs(widget)
if isinstance(widget, (RadioSelect, CheckboxSelectMultiple)):
widget.template_name = "django_bootstrap5/widgets/radio_select.html"
elif isinstance(widget, ClearableFileInput):
widget.template_name = "django_bootstrap5/widgets/clearable_file_input.html"

def list_to_class(self, html, klass):
classes = merge_css_classes(klass, self.get_size_class())
Expand Down Expand Up @@ -359,10 +358,6 @@ def fix_date_select_input(self, html):
html = html.replace("</select>", "</select>" + div2)
return '<div class="row django_bootstrap5-multi-input">{html}</div>'.format(html=html)

def fix_file_input_label(self, html):
html = "<br>" + html
return html

def post_widget_render(self, html):
if isinstance(self.widget, RadioSelect):
html = self.list_to_class(html, "radio radio-success")
Expand All @@ -372,8 +367,6 @@ def post_widget_render(self, html):
html = self.fix_date_select_input(html)
elif isinstance(self.widget, CheckboxInput):
html = self.add_checkbox_label(html)
elif isinstance(self.widget, FileInput):
html = self.fix_file_input_label(html)
return html

def wrap_widget(self, html):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<input type="{{ widget.type }}" name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>
{% if widget.is_initial %}
<div class="row mt-1">
<div class="col-auto">
{{ widget.initial_text }}:&nbsp;<a href="{{ widget.value.url }}">{{ widget.value }}</a>
</div>
{% if not widget.required %}
<div class="col-auto">
<div class="form-check">
<input type="checkbox" name="{{ widget.checkbox_name }}" id="{{ widget.checkbox_id }}"
class="form-check-input"
{% if widget.attrs.disabled %} disabled{% endif %}>
<label for="{{ widget.checkbox_id }}"
class="form-check-label">{{ widget.clear_checkbox_label }}</label>
</div>
</div>
{% endif %}
</div>
{% endif %}

Empty file added tests/foo.txt
Empty file.
56 changes: 52 additions & 4 deletions tests/test_bootstrap_field.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django import forms
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase

from django_bootstrap5.exceptions import BootstrapError
Expand All @@ -16,16 +17,63 @@
class BootstrapFieldTest(TestCase):
def test_bootstrap_field_text(self):
class TestForm(forms.Form):
name = forms.CharField()
test = forms.CharField()

test_form = TestForm()
html = render_template_with_bootstrap("{% bootstrap_field form.name %}", context={"form": test_form})
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_name" class="form-label">Name</label>'
'<input class="form-control" id="id_name" name="name" placeholder="Name" required type="text">'
'<label for="id_test" class="form-label">Test</label>'
'<input class="form-control" id="id_test" name="test" placeholder="Test" required type="text">'
"</div>"
),
)

def test_bootstrap_field_file(self):
class TestForm(forms.Form):
test = forms.FileField()

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" id="id_test" name="test" required type="file">'
"</div>"
),
)

def test_bootstrap_field_clearable_file(self):
class TestForm(forms.Form):
test = forms.FileField(widget=forms.ClearableFileInput, required=False)

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

def test_bootstrap_field_clearable_file_post(self):
class TestForm(forms.Form):
test = forms.FileField(widget=forms.ClearableFileInput, required=False)

test_form = TestForm({}, {"test": SimpleUploadedFile("test.txt", b"test")})
print(render_template_with_bootstrap("{% bootstrap_field form.test %}", context={"form": test_form}))
self.assertHTMLEqual(
render_template_with_bootstrap("{% bootstrap_field form.test %}", context={"form": test_form}),
(
'<div class="django_bootstrap5-bound mb-3">'
'<label class="form-label" for="id_test">Test</label>'
'<input type="file" name="test" class="form-control django_bootstrap5-bound" id="id_test">'
"</div>"
),
)
Expand Down

0 comments on commit c8197ee

Please sign in to comment.