From 7442934bf627845937462954fd2592b9e13acce0 Mon Sep 17 00:00:00 2001 From: Vitaliy Kucheriavyi Date: Fri, 12 Jan 2018 15:24:48 +0200 Subject: [PATCH 1/6] {% bootstrap %} tag support --- CHANGES.txt | 3 + README.rst | 29 ++++ bootstrapform/fixtures/bootstrap_tag.html | 191 ++++++++++++++++++++++ bootstrapform/templatetags/bootstrap.py | 51 +++++- bootstrapform/tests.py | 27 ++- 5 files changed, 297 insertions(+), 4 deletions(-) create mode 100644 bootstrapform/fixtures/bootstrap_tag.html diff --git a/CHANGES.txt b/CHANGES.txt index 43df05b..761a5c6 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +- 2018-01-12: + - {% bootstrap %} tag by @vital1k + - 2015-03-09: - Fix unit test fail with Django 1.7 @nikolas diff --git a/README.rst b/README.rst index 516ee0e..0fa5300 100644 --- a/README.rst +++ b/README.rst @@ -37,6 +37,8 @@ At the top of your template load in our template tags:: Then to render your form:: +###### Vertical +
Form Title {% csrf_token %} @@ -50,6 +52,9 @@ You can also set class="form-vertical" on the form element. To use class="form-inline" on the form element, also change the "|boostrap" template tag to "|bootstrap_inline". + +###### Horizontal + 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:: @@ -64,6 +69,30 @@ It is also possible to create a horizontal form. The form class and template tag
+###### Custom/Grid Layout + + For custom layout - use `{% bootstrap %}` *tag* - each line in it represent bootstrap .row with fields separted by space: + +
+ Form Title + {% csrf_token %} + + {% bootstrap form %} + char_field choice_field radio_choice + multiple_choice multiple_checkbox + file_fied password_field + textarea + boolean_field + {% endbootstrap %} + +
+
+ +
+
+
+ + Demo ===== diff --git a/bootstrapform/fixtures/bootstrap_tag.html b/bootstrapform/fixtures/bootstrap_tag.html new file mode 100644 index 0000000..1c8d0ea --- /dev/null +++ b/bootstrapform/fixtures/bootstrap_tag.html @@ -0,0 +1,191 @@ +
+
+ +
+ + + + + +
+ + +
+ +
+
+ +
+ + + + + +
+ + +
+ +
+
+ +
+ + + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ + +
+ +
+
+
+ +
+
+ +
+ + + + + +
+ + + +
+ +
+
+ +
+ + + + + +
+
  • +
  • +
+ + + + +
+ +
+
+
+ +
+
+ +
+ + + + + +
+ + + + + +
+ +
+
+ +
+ + + + + +
+ + + + + +
+ +
+
+
+ + +
+ +
+ +
+ + + + + +
+ + + +
+ +
+
+
+ +
+
+ +
+ +
+
+ + + + +
+
+ +
+
+
diff --git a/bootstrapform/templatetags/bootstrap.py b/bootstrapform/templatetags/bootstrap.py index b0dcc75..66ef274 100644 --- a/bootstrapform/templatetags/bootstrap.py +++ b/bootstrapform/templatetags/bootstrap.py @@ -8,6 +8,7 @@ register = template.Library() + @register.filter def bootstrap(element): markup_classes = {'label': '', 'value': '', 'single_value': ''} @@ -78,7 +79,6 @@ def render(element, markup_classes): template = get_template("bootstrapform/form.html") context = {'form': element, 'classes': markup_classes} - if django_version < (1, 8): context = Context(context) @@ -103,3 +103,52 @@ def is_radio(field): @register.filter def is_file(field): return isinstance(field.field.widget, forms.FileInput) + + +# {% bootstrap %} tag + +@register.tag(name='bootstrap') +def bootstrap_tag(parser, token): + nodelist = parser.parse(('endbootstrap',)) + try: + # Splitting by None == splitting by spaces. + tag_name, form = token.contents.split(None, 1) + except ValueError: + raise template.TemplateSyntaxError( + "%r tag requires a form arguments" % token.contents.split()[0] + ) + parser.delete_first_token() + return Bootstrap(nodelist, form) + + +class Bootstrap(template.Node): + def __init__(self, nodelist, form): + self.nodelist = nodelist + self.form_variable = form + + def render(self, context): + tag_contents = self.nodelist.render(context) + self.form = context[self.form_variable] + return ''.join(self._get_rows(tag_contents)) + + def _get_rows(self, tag_contents): + for i in tag_contents.splitlines(): + row = i.strip() + if not row: + continue + output = [ + '
', + ''.join(self._get_fields(row)), + '
', + ] + yield ''.join(output) + + def _get_fields(self, row): + field_names = [i.strip() for i in row.split(' ') if i.strip()] + col_class = 'col-%d' % (12 // len(field_names)) + for f in field_names: + try: + f = self.form[f] + except KeyError as e: + raise Exception('Failed to process line\n{}\n{}'.format(row, e)) + yield '
{}
'.format(col_class, bootstrap(f)) diff --git a/bootstrapform/tests.py b/bootstrapform/tests.py index f56f39b..883e062 100644 --- a/bootstrapform/tests.py +++ b/bootstrapform/tests.py @@ -12,8 +12,8 @@ CHOICES = ( - (0, 'Zero'), - (1, 'One'), + (0, 'Zero'), + (1, 'One'), (2, 'Two'), ) @@ -23,6 +23,7 @@ except: pass + class ExampleForm(forms.Form): char_field = forms.CharField(required=False) choice_field = forms.ChoiceField(choices=CHOICES, required=False) @@ -43,7 +44,6 @@ def test_basic_form(self): html = Template("{% load bootstrap %}{{ form|bootstrap }}").render(Context({'form': form})) - if StrictVersion(django.get_version()) >= StrictVersion('1.7'): fixture = 'basic.html' elif StrictVersion(django.get_version()) >= StrictVersion('1.6'): @@ -80,3 +80,24 @@ def test_bound_field(self): self.assertTrue(form.is_bound) rendered_template = bootstrap.bootstrap(form['char_field']) + + def test_bootstrap_tag(self): + form = ExampleForm() + + tpl_str = """ + {% load bootstrap %} + {% bootstrap form %} + char_field choice_field radio_choice + multiple_choice multiple_checkbox + file_fied password_field + textarea + boolean_field + {% endbootstrap %} + """ + html = Template(tpl_str).render(Context({'form': form})) + + tpl = os.path.join('fixtures', 'bootstrap_tag.html') + with open(os.path.join(TEST_DIR, tpl)) as f: + content = f.read() + + self.assertHTMLEqual(html, content) From efae5baf90624b3c4d7740563906c4d1a006d7a8 Mon Sep 17 00:00:00 2001 From: Vitaliy Kucheryaviy Date: Fri, 12 Jan 2018 16:06:39 +0200 Subject: [PATCH 2/6] Update README.rst --- README.rst | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/README.rst b/README.rst index 0fa5300..361e1c9 100644 --- a/README.rst +++ b/README.rst @@ -34,11 +34,12 @@ Add "bootstrapform" to your INSTALLED_APPS. At the top of your template load in our template tags:: {% load bootstrap %} + +Vertical +~~~~~~~~~~~~~~~~~ Then to render your form:: -###### Vertical -
Form Title {% csrf_token %} @@ -52,8 +53,8 @@ You can also set class="form-vertical" on the form element. To use class="form-inline" on the form element, also change the "|boostrap" template tag to "|bootstrap_inline". - -###### Horizontal +Horizontal +~~~~~~~~~~~~~~~~~ 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:: @@ -69,22 +70,15 @@ It is also possible to create a horizontal form. The form class and template tag
-###### Custom/Grid Layout +Custom Layout +~~~~~~~~~~~~~~~~~ - For custom layout - use `{% bootstrap %}` *tag* - each line in it represent bootstrap .row with fields separted by space: +For custom layout - use {% bootstrap %} tag - each line in it represent bootstrap .row with fields separted by space::
Form Title {% csrf_token %} - - {% bootstrap form %} - char_field choice_field radio_choice - multiple_choice multiple_checkbox - file_fied password_field - textarea - boolean_field - {% endbootstrap %} - + {{ form|bootstrap_horizontal }}
@@ -92,7 +86,6 @@ It is also possible to create a horizontal form. The form class and template tag
- Demo ===== From b49e26bb3e89f158de0eee671071f87e1be81855 Mon Sep 17 00:00:00 2001 From: Vitaliy Kucheryaviy Date: Fri, 12 Jan 2018 16:08:05 +0200 Subject: [PATCH 3/6] Update README.rst --- README.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 361e1c9..bbc39e0 100644 --- a/README.rst +++ b/README.rst @@ -78,7 +78,13 @@ For custom layout - use {% bootstrap %} tag - each line in it represent bootstra
Form Title {% csrf_token %} - {{ form|bootstrap_horizontal }} + {% bootstrap form %} + char_field choice_field radio_choice + multiple_choice multiple_checkbox + file_fied password_field + textarea + boolean_field + {% endbootstrap %}
From c33492a6eaa50c410820494ff45253a732b52c22 Mon Sep 17 00:00:00 2001 From: Vitaliy Kucheriavyi Date: Fri, 12 Jan 2018 16:18:32 +0200 Subject: [PATCH 4/6] Graphical examples --- README.rst | 10 +++++++--- docs/_static/bootstrap_tag.png | Bin 0 -> 42883 bytes 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 docs/_static/bootstrap_tag.png diff --git a/README.rst b/README.rst index bbc39e0..410d954 100644 --- a/README.rst +++ b/README.rst @@ -79,9 +79,9 @@ For custom layout - use {% bootstrap %} tag - each line in it represent bootstra Form Title {% csrf_token %} {% bootstrap form %} - char_field choice_field radio_choice - multiple_choice multiple_checkbox - file_fied password_field + char_field choice_field + radio_choice multiple_choice multiple_checkbox + password_field file_fied textarea boolean_field {% endbootstrap %} @@ -92,6 +92,10 @@ For custom layout - use {% bootstrap %} tag - each line in it represent bootstra
+Will result layout like this + +.. image:: docs/_static/bootstrap_tag.png + Demo ===== diff --git a/docs/_static/bootstrap_tag.png b/docs/_static/bootstrap_tag.png new file mode 100644 index 0000000000000000000000000000000000000000..2bc65fa9bbd38997b21059c54068df1109d1998d GIT binary patch literal 42883 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 literal 0 HcmV?d00001 From 56708becd5d77faf2f25eb0b81b3c26ad48a8c07 Mon Sep 17 00:00:00 2001 From: Vitaliy Kucheriavyi Date: Fri, 12 Jan 2018 16:38:08 +0200 Subject: [PATCH 5/6] tests for django 1.5, 1.6 --- .../fixtures/bootstrap_tag_dj16.html | 190 ++++++++++++++++++ bootstrapform/fixtures/bootstrap_tag_old.html | 190 ++++++++++++++++++ bootstrapform/tests.py | 9 +- 3 files changed, 388 insertions(+), 1 deletion(-) create mode 100644 bootstrapform/fixtures/bootstrap_tag_dj16.html create mode 100644 bootstrapform/fixtures/bootstrap_tag_old.html diff --git a/bootstrapform/fixtures/bootstrap_tag_dj16.html b/bootstrapform/fixtures/bootstrap_tag_dj16.html new file mode 100644 index 0000000..4125f7e --- /dev/null +++ b/bootstrapform/fixtures/bootstrap_tag_dj16.html @@ -0,0 +1,190 @@ +
+
+ +
+ + + + + +
+ + + + + +
+ +
+
+ +
+ + + + + +
+ + + + + +
+ +
+
+ +
+ + + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ + + + + +
+ +
+
+ +
+ + + + + +
+ + + + + +
+ +
+
+ +
+ + + + + +
+
    +
  • +
  • +
  • +
+ + + + +
+ +
+
+ +
+ + + + + +
+ + + + + +
+ +
+
+ +
+ + + + + +
+ + + + + +
+ +
+
+ +
+ + + + + +
+ + + + + +
+ +
+
+ +
+ +
+
+ + + + + + +
+
+ +
+
+
\ No newline at end of file diff --git a/bootstrapform/fixtures/bootstrap_tag_old.html b/bootstrapform/fixtures/bootstrap_tag_old.html new file mode 100644 index 0000000..c3f4981 --- /dev/null +++ b/bootstrapform/fixtures/bootstrap_tag_old.html @@ -0,0 +1,190 @@ +
+
+ +
+ + + + + +
+ + + + + +
+ +
+
+ +
+ + + + + +
+ + + + + +
+ +
+
+ +
+ + + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ + + + + +
+ +
+
+ +
+ + + + + +
+ + + + + +
+ +
+
+ +
+ + + + + +
+
    +
  • +
  • +
  • +
+ + + + +
+ +
+
+ +
+ + + + + +
+ + + + + +
+ +
+
+ +
+ + + + + +
+ + + + + +
+ +
+
+ +
+ + + + + +
+ + + + + +
+ +
+
+ +
+ +
+
+ + + + + + +
+
+ +
+
+
\ No newline at end of file diff --git a/bootstrapform/tests.py b/bootstrapform/tests.py index 883e062..da3d47b 100644 --- a/bootstrapform/tests.py +++ b/bootstrapform/tests.py @@ -96,7 +96,14 @@ def test_bootstrap_tag(self): """ html = Template(tpl_str).render(Context({'form': form})) - tpl = os.path.join('fixtures', 'bootstrap_tag.html') + if StrictVersion(django.get_version()) >= StrictVersion('1.7'): + fixture = 'bootstrap_tag.html' + elif StrictVersion(django.get_version()) >= StrictVersion('1.6'): + fixture = 'bootstrap_tag_dj16.html' + else: + fixture = 'bootstrap_tag_old.html' + + tpl = os.path.join('fixtures', fixture) with open(os.path.join(TEST_DIR, tpl)) as f: content = f.read() From a6b5784f16869d4f3dc31a9ac1a18ae11371e953 Mon Sep 17 00:00:00 2001 From: Vitaliy Kucheriavyi Date: Tue, 26 Jun 2018 13:48:17 -0700 Subject: [PATCH 6/6] allow passing col size for each field {% bootstrap form %} fieldA(4) fieldB(8) fieldC fieldD(2) {% endbootstrap %} --- bootstrapform/templatetags/bootstrap.py | 31 +++++++++++++++++++------ 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/bootstrapform/templatetags/bootstrap.py b/bootstrapform/templatetags/bootstrap.py index 66ef274..277bac0 100644 --- a/bootstrapform/templatetags/bootstrap.py +++ b/bootstrapform/templatetags/bootstrap.py @@ -1,3 +1,4 @@ +import re import django from django import forms, VERSION as django_version from django.template import Context @@ -47,6 +48,7 @@ def bootstrap_horizontal(element, label_cols='col-sm-2 col-lg-2'): return render(element, markup_classes) + @register.filter def add_input_classes(field): if not is_checkbox(field) and not is_multiple_checkbox(field) \ @@ -132,10 +134,7 @@ def render(self, context): return ''.join(self._get_rows(tag_contents)) def _get_rows(self, tag_contents): - for i in tag_contents.splitlines(): - row = i.strip() - if not row: - continue + for row in self._parse_fields(tag_contents): output = [ '
', ''.join(self._get_fields(row)), @@ -144,11 +143,29 @@ def _get_rows(self, tag_contents): yield ''.join(output) def _get_fields(self, row): - field_names = [i.strip() for i in row.split(' ') if i.strip()] - col_class = 'col-%d' % (12 // len(field_names)) - for f in field_names: + for f, size in row: + col_class = 'col' + if size: + col_class += '-md-' + size try: f = self.form[f] except KeyError as e: raise Exception('Failed to process line\n{}\n{}'.format(row, e)) yield '
{}
'.format(col_class, bootstrap(f)) + + def _parse_fields(self, tag_contents): + result = [] + for line in tag_contents.splitlines(): + line = line.strip() + if not line: + continue + field_names = [i.strip() for i in line.split(' ') if i.strip()] + row = [] + for name in field_names: + if '(' in name: + name, col_size = re.findall(r'^(.*?)\((\d+)\)$', name)[0] + else: + col_size = None + row.append((name, col_size)) + result.append(row) + return result