Skip to content
This repository was archived by the owner on Feb 11, 2020. It is now read-only.

Commit 38f8f5e

Browse files
committed
Implemented container, row and column node classes supporting bootstrap's grid system
1 parent 20608d2 commit 38f8f5e

File tree

4 files changed

+231
-4
lines changed

4 files changed

+231
-4
lines changed

bootstrap_ui/templatetags/bootstrap_ui_tags.py

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from django.template import Library, TemplateSyntaxError
1+
from django.template import Library, Node, TemplateSyntaxError
22
from django.utils.safestring import mark_safe
33
from dominate import tags
44
from tag_parser import template_tag
@@ -82,6 +82,71 @@ class BootstrapNode(HtmlTagNode):
8282
default_tag = 'div'
8383

8484

85+
@template_tag(register, 'column')
86+
class ColumnNode(BootstrapNode):
87+
"""Renders a column"""
88+
# Overwrite BaseNode attributes
89+
allowed_kwargs = ('xs', 'sm', 'md', 'lg')
90+
end_tag_name = 'endcolumn'
91+
92+
# Overwrite HtmlTagNode attributes
93+
default_css_classes = ['col-xs-12']
94+
default_tag = 'div'
95+
96+
def render_tag(self, context, *tag_args, **tag_kwargs):
97+
htmltag = super(BootstrapNode, self).render_tag(context, safe=False, *tag_args, **tag_kwargs)
98+
scope = context.render_context[self]
99+
100+
# Apply column grid classes
101+
apply_grid_classes = list(set(scope) & set(self.allowed_kwargs))
102+
103+
if apply_grid_classes:
104+
htmltag.set_attribute(
105+
'class',
106+
' '.join('col-' + grid_class + '-' + scope[grid_class] for grid_class in apply_grid_classes)
107+
)
108+
109+
return mark_safe(htmltag.render())
110+
111+
112+
@template_tag(register, 'row')
113+
class RowNode(BootstrapNode):
114+
"""Renders a row"""
115+
# Overwrite BaseNode attributes
116+
allowed_kwargs = ()
117+
end_tag_name = 'endrow'
118+
119+
# Overwrite HtmlTagNode attributes
120+
default_css_classes = ['row']
121+
default_tag = 'div'
122+
123+
124+
@template_tag(register, 'container')
125+
class ContainerNode(BootstrapNode):
126+
"""Renders a container"""
127+
# Overwrite BaseNode attributes
128+
allowed_kwargs = ('type',)
129+
end_tag_name = 'endcontainer'
130+
131+
# Overwrite HtmlTagNode attributes
132+
default_css_classes = ['container']
133+
default_tag = 'div'
134+
135+
def render_tag(self, context, *tag_args, **tag_kwargs):
136+
htmltag = super(ContainerNode, self).render_tag(context, safe=False, *tag_args, **tag_kwargs)
137+
scope = context.render_context[self]
138+
139+
if 'type' in scope:
140+
if scope['type'] != 'fluid':
141+
raise TemplateSyntaxError(
142+
'%r tag only allows %r for %r' % (self.tag_name, 'fluid', 'type')
143+
)
144+
145+
htmltag.set_attribute('class', 'container-fluid')
146+
147+
return mark_safe(htmltag.render())
148+
149+
85150
@template_tag(register, 'listgroup')
86151
class ListGroupNode(BootstrapNode):
87152
"""Renders a list group"""

docs/components/index.rst

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,92 @@
11
Available bootstrap components
22
==============================
33

4+
Grid system
5+
-----------
6+
7+
See http://getbootstrap.com/css/#grid.
8+
9+
Container
10+
*********
11+
12+
Use the ``container`` tag to house a fixed-width bootstrap grid system::
13+
14+
{% container %}
15+
...
16+
{% endcontainer %}
17+
18+
This renders the following html code::
19+
20+
<div class="container">
21+
...
22+
</div>
23+
24+
Provide ``type="fluid"`` as parameter for a full-width container housing your grid::
25+
26+
{% container type="fluid" %}
27+
...
28+
{% endcontainer %}
29+
30+
This renders the following html code::
31+
32+
<div class="container-fluid">
33+
...
34+
</div>
35+
36+
Row
37+
***
38+
39+
Use ``row`` to create horizontal groups of columns within containers::
40+
41+
{% row %}
42+
...
43+
{% endrow %}
44+
45+
This renders the following html code::
46+
47+
<div class="row">
48+
...
49+
</div>
50+
51+
Remember that according to the bootstrap rules only columns may be immediate children of rows.
52+
53+
Column
54+
******
55+
56+
Basic example
57+
+++++++++++++
58+
59+
Place ``column`` within rows to span a certain width of your row::
60+
61+
{% column %}
62+
Lorem ipsum. Your content goes here!
63+
{% endcolumn %}
64+
65+
This renders the following html code::
66+
67+
<div class="col-xs-12">
68+
Lorem ipsum. Your content goes here!
69+
</div>
70+
71+
As bootstrap is a mobile first framework grid classes are applied to devices with screen widths greater than or equal to the breakpoint sizes. Therefore ``column`` used without parameters applies a ``col-xs-12`` css class.
72+
73+
Custom column width and larger devices
74+
++++++++++++++++++++++++++++++++++++++
75+
76+
Provide ``xs``, ``sm``, ``md`` and/or ``lg`` parameters to change the column span and address larger viewports::
77+
78+
{% column xs="8" sm="6" md="4" lg="3" %}
79+
Lorem ipsum. Your content goes here!
80+
{% endcolumn %}
81+
82+
This renders the following html code::
83+
84+
<div class="col-xs-8 col-sm-6 col-md-4 col-lg-3">
85+
Lorem ipsum. Your content goes here!
86+
</div>
87+
88+
Every individual parameter may be omitted, you can use any combination of them.
89+
490
List group
591
----------
692

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
setup(
1111
name='django-bootstrap-ui',
12-
version='0.1.0a5',
12+
version='0.1.0b1',
1313
packages=find_packages(exclude=['tests', 'docs']),
1414
include_package_data=True,
1515
license='ISC License (ISCL)',
@@ -25,7 +25,7 @@
2525
'dominate>=2.1,<2.2',
2626
],
2727
classifiers=[
28-
'Development Status :: 3 - Alpha',
28+
'Development Status :: 4 - Beta',
2929
'Environment :: Web Environment',
3030
'Framework :: Django',
3131
'Framework :: Django :: 1.7',

tests/tests.py

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,80 @@ def test_bootstraptag_is_rendered(self):
8686
self.assertInHTML(self.HTMLTAG_START + self.HTMLTAG_END, rendered)
8787

8888

89+
class GridTagsTest(SimpleTestCase):
90+
SAMPLE_CONTENT = 'Lorem ipsum'
91+
SAMPLE_COLUMN_WIDTH = '7'
92+
93+
CONTAINER_START = mark_safe('<div class="container">')
94+
CONTAINER_END = mark_safe('</div>')
95+
96+
CONTAINER_FLUID_START = mark_safe('<div class="container-fluid">')
97+
CONTAINER_FLUID_END = mark_safe('</div>')
98+
99+
ROW_START = mark_safe('<div class="row">')
100+
ROW_END = mark_safe('</div>')
101+
102+
COLUMN_START = mark_safe('<div class="col-xs-12">')
103+
COLUMN_END = mark_safe('</div>')
104+
105+
COLUMN_CUSTOM_WIDTH_START = mark_safe(
106+
'<div class="'
107+
+ 'col-xs-' + SAMPLE_COLUMN_WIDTH
108+
+ ' col-sm-' + SAMPLE_COLUMN_WIDTH
109+
+ ' col-md-' + SAMPLE_COLUMN_WIDTH
110+
+ ' col-lg-' + SAMPLE_COLUMN_WIDTH
111+
+ '">'
112+
)
113+
COLUMN_CUSTOM_WIDTH_END = mark_safe('</div>')
114+
115+
TEMPLATE_SIMPLE = Template(
116+
'{% load bootstrap_ui_tags %}'
117+
'{% container %}'
118+
'{% row %}'
119+
'{% column %}'
120+
'{{ content }}'
121+
'{% endcolumn %}'
122+
'{% endrow %}'
123+
'{% endcontainer %}'
124+
'{% container type=type %}'
125+
'{% endcontainer %}'
126+
)
127+
128+
TEMPLATE_CUSTOM_COLUMN = Template(
129+
'{% load bootstrap_ui_tags %}'
130+
'{% column xs=xs sm=sm md=md lg=lg %}'
131+
'{% endcolumn %}'
132+
)
133+
134+
def setUp(self):
135+
pass
136+
137+
def test_container_with_row_column_and_content_is_rendered(self):
138+
rendered = self.TEMPLATE_SIMPLE.render(Context({'content': self.SAMPLE_CONTENT, 'type': 'fluid'}))
139+
self.assertInHTML(
140+
self.CONTAINER_START + self.ROW_START + self.COLUMN_START + self.SAMPLE_CONTENT + self.COLUMN_END
141+
+ self.ROW_END + self.CONTAINER_END,
142+
rendered
143+
)
144+
145+
def test_container_fluid_is_rendered(self):
146+
rendered = self.TEMPLATE_SIMPLE.render(Context({'type': 'fluid'}))
147+
self.assertInHTML(self.CONTAINER_FLUID_START + self.CONTAINER_FLUID_END, rendered)
148+
149+
def test_container_invalid_type_raises_exception(self):
150+
with self.assertRaises(TemplateSyntaxError):
151+
self.TEMPLATE_SIMPLE.render(Context({'type': 'foo'}))
152+
153+
def test_column_custom_grid_is_rendered(self):
154+
rendered = self.TEMPLATE_CUSTOM_COLUMN.render(Context({
155+
'xs': self.SAMPLE_COLUMN_WIDTH,
156+
'sm': self.SAMPLE_COLUMN_WIDTH,
157+
'md': self.SAMPLE_COLUMN_WIDTH,
158+
'lg': self.SAMPLE_COLUMN_WIDTH
159+
}))
160+
self.assertInHTML(self.COLUMN_CUSTOM_WIDTH_START + self.COLUMN_CUSTOM_WIDTH_END, rendered)
161+
162+
89163
class ListGroupTagsTest(SimpleTestCase):
90164
SAMPLE_LINK = 'http://example.org'
91165
SAMPLE_LABEL = 'Linklabel'
@@ -135,7 +209,9 @@ def test_list_group_item_default_tag_is_rendered(self):
135209
self.assertInHTML(self.LIST_GROUP_ITEM_START + self.LIST_GROUP_ITEM_END, rendered)
136210

137211
def test_list_group_item_link_is_rendered(self):
138-
rendered = self.TEMPLATE_LINK.render(Context({'sample_link': self.SAMPLE_LINK, 'sample_label': self.SAMPLE_LABEL}))
212+
rendered = self.TEMPLATE_LINK.render(
213+
Context({'sample_link': self.SAMPLE_LINK, 'sample_label': self.SAMPLE_LABEL})
214+
)
139215
self.assertInHTML(self.LIST_GROUP_ITEM_LINK_START + self.SAMPLE_LABEL + self.LIST_GROUP_ITEM_LINK_END, rendered)
140216

141217
def test_list_group_item_link_without_destination_raises_exception(self):

0 commit comments

Comments
 (0)