Skip to content

Commit 7442934

Browse files
committed
{% bootstrap %} tag support
1 parent 2b0aff1 commit 7442934

File tree

5 files changed

+297
-4
lines changed

5 files changed

+297
-4
lines changed

CHANGES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
- 2018-01-12:
2+
- {% bootstrap %} tag by @vital1k
3+
14
- 2015-03-09:
25

36
- Fix unit test fail with Django 1.7 @nikolas

README.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ At the top of your template load in our template tags::
3737

3838
Then to render your form::
3939

40+
###### Vertical
41+
4042
<form role="form">
4143
<legend>Form Title</legend>
4244
{% csrf_token %}
@@ -50,6 +52,9 @@ You can also set class="form-vertical" on the form element.
5052

5153
To use class="form-inline" on the form element, also change the "|boostrap" template tag to "|bootstrap_inline".
5254
55+
56+
###### Horizontal
57+
5358
It is also possible to create a horizontal form. The form class and template tag are both changed, and you will also need slightly different CSS around the submit button::
5459

5560
<form class="form-horizontal">
@@ -64,6 +69,30 @@ It is also possible to create a horizontal form. The form class and template tag
6469
</form>
6570

6671

72+
###### Custom/Grid Layout
73+
74+
For custom layout - use `{% bootstrap %}` *tag* - each line in it represent bootstrap .row with fields separted by space:
75+
76+
<form class="form-horizontal">
77+
<legend>Form Title</legend>
78+
{% csrf_token %}
79+
80+
{% bootstrap form %}
81+
char_field choice_field radio_choice
82+
multiple_choice multiple_checkbox
83+
file_fied password_field
84+
textarea
85+
boolean_field
86+
{% endbootstrap %}
87+
88+
<div class="form-group">
89+
<div class="col-sm-10 col-sm-offset-2">
90+
<button type="submit" class="btn btn-primary">Submit</button>
91+
</div>
92+
</div>
93+
</form>
94+
95+
6796
Demo
6897
=====
6998

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
<div class="row">
2+
<div class="col-4">
3+
4+
<div class="form-group">
5+
6+
7+
<label class="control-label " for="id_char_field">Char field</label>
8+
9+
10+
<div class=" ">
11+
<input class=" form-control" id="id_char_field" name="char_field" type="text" />
12+
13+
</div>
14+
15+
</div>
16+
</div><div class="col-4">
17+
18+
<div class="form-group">
19+
20+
21+
<label class="control-label " for="id_choice_field">Choice field</label>
22+
23+
24+
<div class=" ">
25+
<select class=" form-control" id="id_choice_field" name="choice_field">
26+
<option value="0">Zero</option>
27+
<option value="1">One</option>
28+
<option value="2">Two</option>
29+
</select>
30+
31+
</div>
32+
33+
</div>
34+
</div><div class="col-4">
35+
36+
<div class="form-group">
37+
38+
39+
<label class="control-label ">Radio choice</label>
40+
41+
<div class="">
42+
43+
<div class="radio">
44+
<label>
45+
<input id="id_radio_choice_0" name="radio_choice" type="radio" value="0" />
46+
Zero
47+
</label>
48+
</div>
49+
50+
<div class="radio">
51+
<label>
52+
<input id="id_radio_choice_1" name="radio_choice" type="radio" value="1" />
53+
One
54+
</label>
55+
</div>
56+
57+
<div class="radio">
58+
<label>
59+
<input id="id_radio_choice_2" name="radio_choice" type="radio" value="2" />
60+
Two
61+
</label>
62+
</div>
63+
64+
65+
</div>
66+
67+
</div>
68+
</div>
69+
</div>
70+
71+
<div class="row">
72+
<div class="col-6">
73+
74+
<div class="form-group">
75+
76+
77+
<label class="control-label " for="id_multiple_choice">Multiple choice</label>
78+
79+
80+
<div class=" ">
81+
<select multiple="multiple" class=" form-control" id="id_multiple_choice" name="multiple_choice">
82+
<option value="0">Zero</option>
83+
<option value="1">One</option>
84+
<option value="2">Two</option>
85+
</select>
86+
87+
88+
</div>
89+
90+
</div>
91+
</div><div class="col-6">
92+
93+
<div class="form-group">
94+
95+
96+
<label class="control-label " for="id_multiple_checkbox">Multiple checkbox</label>
97+
98+
99+
<div class=" multiple-checkbox">
100+
<ul id="id_multiple_checkbox"><li><label for="id_multiple_checkbox_0"><input id="id_multiple_checkbox_0" name="multiple_checkbox" type="checkbox" value="0" /> Zero</label></li>
101+
<li><label for="id_multiple_checkbox_1"><input id="id_multiple_checkbox_1" name="multiple_checkbox" type="checkbox" value="1" /> One</label></li>
102+
<li><label for="id_multiple_checkbox_2"><input id="id_multiple_checkbox_2" name="multiple_checkbox" type="checkbox" value="2" /> Two</label></li></ul>
103+
104+
105+
106+
107+
</div>
108+
109+
</div>
110+
</div>
111+
</div>
112+
113+
<div class="row">
114+
<div class="col-6">
115+
116+
<div class="form-group">
117+
118+
119+
<label class="control-label " for="id_file_fied">File fied</label>
120+
121+
122+
<div class=" ">
123+
<input id="id_file_fied" name="file_fied" type="file" />
124+
125+
126+
127+
128+
</div>
129+
130+
</div>
131+
</div><div class="col-6">
132+
133+
<div class="form-group">
134+
135+
136+
<label class="control-label " for="id_password_field">Password field</label>
137+
138+
139+
<div class=" ">
140+
<input class=" form-control" id="id_password_field" name="password_field" type="password" />
141+
142+
143+
144+
145+
</div>
146+
147+
</div>
148+
</div>
149+
</div>
150+
151+
152+
<div class="row">
153+
154+
<div class="col-12">
155+
156+
<div class="form-group">
157+
158+
159+
<label class="control-label " for="id_textarea">Textarea</label>
160+
161+
162+
<div class=" ">
163+
<textarea class=" form-control" cols="40" id="id_textarea" name="textarea" rows="10">
164+
</textarea>
165+
166+
167+
</div>
168+
169+
</div>
170+
</div>
171+
</div>
172+
173+
<div class="row">
174+
<div class="col-12">
175+
176+
<div class="form-group">
177+
178+
<div class="">
179+
<div class="checkbox">
180+
181+
<label >
182+
<input id="id_boolean_field" name="boolean_field" type="checkbox" /> <span>Boolean field</span>
183+
</label>
184+
185+
186+
</div>
187+
</div>
188+
189+
</div>
190+
</div>
191+
</div>

bootstrapform/templatetags/bootstrap.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
register = template.Library()
1010

11+
1112
@register.filter
1213
def bootstrap(element):
1314
markup_classes = {'label': '', 'value': '', 'single_value': ''}
@@ -78,7 +79,6 @@ def render(element, markup_classes):
7879
template = get_template("bootstrapform/form.html")
7980
context = {'form': element, 'classes': markup_classes}
8081

81-
8282
if django_version < (1, 8):
8383
context = Context(context)
8484

@@ -103,3 +103,52 @@ def is_radio(field):
103103
@register.filter
104104
def is_file(field):
105105
return isinstance(field.field.widget, forms.FileInput)
106+
107+
108+
# {% bootstrap %} tag
109+
110+
@register.tag(name='bootstrap')
111+
def bootstrap_tag(parser, token):
112+
nodelist = parser.parse(('endbootstrap',))
113+
try:
114+
# Splitting by None == splitting by spaces.
115+
tag_name, form = token.contents.split(None, 1)
116+
except ValueError:
117+
raise template.TemplateSyntaxError(
118+
"%r tag requires a form arguments" % token.contents.split()[0]
119+
)
120+
parser.delete_first_token()
121+
return Bootstrap(nodelist, form)
122+
123+
124+
class Bootstrap(template.Node):
125+
def __init__(self, nodelist, form):
126+
self.nodelist = nodelist
127+
self.form_variable = form
128+
129+
def render(self, context):
130+
tag_contents = self.nodelist.render(context)
131+
self.form = context[self.form_variable]
132+
return ''.join(self._get_rows(tag_contents))
133+
134+
def _get_rows(self, tag_contents):
135+
for i in tag_contents.splitlines():
136+
row = i.strip()
137+
if not row:
138+
continue
139+
output = [
140+
'<div class="row">',
141+
''.join(self._get_fields(row)),
142+
'</div>',
143+
]
144+
yield ''.join(output)
145+
146+
def _get_fields(self, row):
147+
field_names = [i.strip() for i in row.split(' ') if i.strip()]
148+
col_class = 'col-%d' % (12 // len(field_names))
149+
for f in field_names:
150+
try:
151+
f = self.form[f]
152+
except KeyError as e:
153+
raise Exception('Failed to process line\n{}\n{}'.format(row, e))
154+
yield '<div class="{}">{}</div>'.format(col_class, bootstrap(f))

bootstrapform/tests.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212

1313

1414
CHOICES = (
15-
(0, 'Zero'),
16-
(1, 'One'),
15+
(0, 'Zero'),
16+
(1, 'One'),
1717
(2, 'Two'),
1818
)
1919

@@ -23,6 +23,7 @@
2323
except:
2424
pass
2525

26+
2627
class ExampleForm(forms.Form):
2728
char_field = forms.CharField(required=False)
2829
choice_field = forms.ChoiceField(choices=CHOICES, required=False)
@@ -43,7 +44,6 @@ def test_basic_form(self):
4344

4445
html = Template("{% load bootstrap %}{{ form|bootstrap }}").render(Context({'form': form}))
4546

46-
4747
if StrictVersion(django.get_version()) >= StrictVersion('1.7'):
4848
fixture = 'basic.html'
4949
elif StrictVersion(django.get_version()) >= StrictVersion('1.6'):
@@ -80,3 +80,24 @@ def test_bound_field(self):
8080

8181
self.assertTrue(form.is_bound)
8282
rendered_template = bootstrap.bootstrap(form['char_field'])
83+
84+
def test_bootstrap_tag(self):
85+
form = ExampleForm()
86+
87+
tpl_str = """
88+
{% load bootstrap %}
89+
{% bootstrap form %}
90+
char_field choice_field radio_choice
91+
multiple_choice multiple_checkbox
92+
file_fied password_field
93+
textarea
94+
boolean_field
95+
{% endbootstrap %}
96+
"""
97+
html = Template(tpl_str).render(Context({'form': form}))
98+
99+
tpl = os.path.join('fixtures', 'bootstrap_tag.html')
100+
with open(os.path.join(TEST_DIR, tpl)) as f:
101+
content = f.read()
102+
103+
self.assertHTMLEqual(html, content)

0 commit comments

Comments
 (0)