Skip to content

Commit 7776ce0

Browse files
authored
Add conditional logic to Fields with tests (#95)
1 parent d2a5653 commit 7776ce0

File tree

55 files changed

+1785
-48
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1785
-48
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1616

1717
## Unreleased
1818

19+
### Fixed
20+
21+
- Conditional fields not working when applied to field objects (e.g. `Field.text`, `Field.checkoxes`) instead of container objects (e.g. `Div`, `Fieldset`) [#95]
22+
1923
## [4.1.0](https://github.com/torchbox/tbxforms/releases/tag/v4.1.0)
2024

2125
### Added

tbxforms/layout/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
from .base import Layout
1+
from .base import (
2+
Layout,
3+
setup_conditional_attrs,
4+
)
25
from .buttons import (
36
Button,
47
Submit,
@@ -30,4 +33,5 @@
3033
"Layout",
3134
"Size",
3235
"Submit",
36+
"setup_conditional_attrs",
3337
]

tbxforms/layout/base.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,34 @@
1+
import json
2+
13
from crispy_forms import layout as crispy_forms_layout
24

35

46
class Layout(crispy_forms_layout.Layout):
57
pass
8+
9+
10+
def setup_conditional_attrs(attrs: dict):
11+
"""
12+
Converts a `data_conditional` or `data-conditional` dict to two separate
13+
JS-friendly variables to handle conditional field/container logic.
14+
15+
As an example, these examples:
16+
* { "data_conditional": { "field_name": "trigger_field", "values": ["yes", "no"] } }
17+
* { "data-conditional": { "field-name": "trigger_field", "values": ["yes", "no"] } }
18+
19+
will be transformed into:
20+
{ "data-conditional-field-name": "trigger_field", "data-conditional-field-values": "[\"yes\", \"no\"]" }
21+
""" # noqa: E501
22+
23+
conditional_attrs = attrs.pop("data_conditional", None) or attrs.pop(
24+
"data-conditional", None
25+
)
26+
if conditional_attrs:
27+
attrs["data-conditional-field-name"] = (
28+
conditional_attrs["field_name"] or conditional_attrs["field-name"]
29+
)
30+
attrs["data-conditional-field-values"] = json.dumps(
31+
conditional_attrs["values"]
32+
)
33+
34+
return attrs

tbxforms/layout/containers.py

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import json
2-
31
from django.template.loader import render_to_string
42

53
from crispy_forms import layout as crispy_forms_layout
@@ -8,7 +6,10 @@
86
flatatt,
97
)
108

11-
from tbxforms.layout import Size
9+
from tbxforms.layout import (
10+
Size,
11+
setup_conditional_attrs,
12+
)
1213

1314

1415
class Div(crispy_forms_layout.Div):
@@ -47,14 +48,8 @@ class Div(crispy_forms_layout.Div):
4748
"""
4849

4950
def __init__(self, *fields, **kwargs):
50-
if "data_conditional" in kwargs:
51-
conditional_attrs = kwargs.pop("data_conditional")
52-
kwargs["data_conditional_field_name"] = conditional_attrs[
53-
"field_name"
54-
]
55-
kwargs["data_conditional_field_values"] = json.dumps(
56-
conditional_attrs["values"]
57-
)
51+
# Setup conditional attributes.
52+
kwargs = setup_conditional_attrs(attrs=kwargs)
5853
super().__init__(*fields, **kwargs)
5954

6055

@@ -128,14 +123,8 @@ def __init__(
128123
if not hasattr(self, "css_class"):
129124
self.css_class = kwargs.pop("css_class", None)
130125

131-
if "data_conditional" in kwargs:
132-
conditional_attrs = kwargs.pop("data_conditional")
133-
kwargs["data_conditional_field_name"] = conditional_attrs[
134-
"field_name"
135-
]
136-
kwargs["data_conditional_field_values"] = json.dumps(
137-
conditional_attrs["values"]
138-
)
126+
# Setup conditional attributes.
127+
kwargs = setup_conditional_attrs(attrs=kwargs)
139128

140129
self.css_id = kwargs.pop("css_id", None)
141130
self.template = kwargs.pop("template", self.template)

tbxforms/layout/fields.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
from django.utils.html import conditional_escape
2-
31
from crispy_forms import layout as crispy_forms_layout
4-
from crispy_forms.utils import TEMPLATE_PACK
2+
from crispy_forms.utils import (
3+
TEMPLATE_PACK,
4+
flatatt,
5+
)
56

67
from tbxforms.layout import (
78
Fixed,
89
Fluid,
910
Size,
11+
setup_conditional_attrs,
1012
)
1113

1214

@@ -421,16 +423,13 @@ def add_attributes(self, **kwargs):
421423
422424
Args:
423425
**kwargs: keyword arguments that will be added as HTML attributes.
426+
"""
424427

425-
Returns:
428+
# Setup conditional attributes.
429+
kwargs = setup_conditional_attrs(attrs=kwargs)
426430

427-
"""
428-
self.attrs.update(
429-
{
430-
k.replace("_", "-"): conditional_escape(v)
431-
for k, v in kwargs.items()
432-
}
433-
)
431+
self.attrs.update(kwargs)
432+
self.flat_attrs = flatatt(self.attrs)
434433

435434
def render(
436435
self,

tbxforms/templates/tbxforms/layout/checkboxes.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
{% if field.help_text or field.errors %}
66
aria-describedby="{% for error in field.errors %}{{ field.auto_id }}_{{ forloop.counter }}_error {% endfor %}{% if field.help_text %}{{ field.auto_id }}_hint{% endif %}"
77
{% endif %}
8+
{{ flat_attrs }}
89
>
910

1011
{% if field.label %}
@@ -25,7 +26,6 @@
2526
<div class="tbxforms-checkboxes{% if inline %}--inline{% endif %}{% if checkboxes_small %} tbxforms-checkboxes--small{% endif %}">
2627
{% for choice in field.field.choices %}
2728
<div class="tbxforms-checkboxes__item">
28-
2929
<input
3030
type="checkbox"
3131
name="{{ field.html_name }}"

tbxforms/templates/tbxforms/layout/multifield.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
{% if field.help_text or field.errors %}
66
aria-describedby="{% for error in field.errors %}{{ field.auto_id }}_{{ forloop.counter }}_error {% endfor %}{% if field.help_text %}{{ field.auto_id }}_hint{% endif %}"
77
{% endif %}
8+
{{ flat_attrs }}
89
>
910

1011
{% if field.label %}

tbxforms/templates/tbxforms/layout/radios.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
{% if field.help_text or field.errors %}
66
aria-describedby="{% for error in field.errors %}{{ field.auto_id }}_{{ forloop.counter }}_error {% endfor %}{% if field.help_text %}{{ field.auto_id }}_hint{% endif %}"
77
{% endif %}
8+
{{ flat_attrs }}
89
>
910

1011
{% if field.label %}

tbxforms/templatetags/tbxforms.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
import ast
2-
import html
3-
import json
4-
51
from django import (
62
forms,
73
template,
@@ -291,18 +287,6 @@ def render(self, context): # noqa: C901
291287

292288
widget.attrs["class"] = css_class
293289

294-
# Convert conditional dict to two separate JS-friendly variables.
295-
if "data-conditional" in widget.attrs:
296-
conditional_attrs = ast.literal_eval(
297-
html.unescape(widget.attrs.pop("data-conditional"))
298-
)
299-
widget.attrs["data-conditional-field-name"] = (
300-
conditional_attrs["field_name"]
301-
)
302-
widget.attrs["data-conditional-field-values"] = json.dumps(
303-
conditional_attrs["values"]
304-
)
305-
306290
# HTML5 required attribute
307291
if (
308292
html5_required
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<form class="tbxforms" method="post">
2+
<div id="div_id_test_field" class="tbxforms-form-group">
3+
<fieldset class="tbxforms-fieldset">
4+
<legend class="tbxforms-fieldset__legend tbxforms-fieldset__legend--m">Test field</legend>
5+
<div class="tbxforms-checkboxes">
6+
<div class="tbxforms-checkboxes__item">
7+
<input type="checkbox"
8+
name="test_field"
9+
class="tbxforms-checkboxes__input"
10+
id="id_test_field_1"
11+
value="yes" />
12+
<label class="tbxforms-label tbxforms-checkboxes__label"
13+
for="id_test_field_1">Yes</label>
14+
</div>
15+
<div class="tbxforms-checkboxes__item">
16+
<input type="checkbox"
17+
name="test_field"
18+
class="tbxforms-checkboxes__input"
19+
id="id_test_field_2"
20+
value="no" />
21+
<label class="tbxforms-label tbxforms-checkboxes__label"
22+
for="id_test_field_2">No</label>
23+
</div>
24+
</div>
25+
</fieldset>
26+
</div>
27+
</form>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<form class="tbxforms" method="post">
2+
<div id="div_id_test_field" class="tbxforms-form-group">
3+
<fieldset class="tbxforms-fieldset">
4+
<legend class="tbxforms-fieldset__legend tbxforms-fieldset__legend--m">Test field</legend>
5+
<div class="tbxforms-radios">
6+
<div class="tbxforms-radios__item">
7+
<input type="radio"
8+
name="test_field"
9+
class="tbxforms-radios__input"
10+
id="id_test_field_1"
11+
value="yes" />
12+
<label class="tbxforms-label tbxforms-radios__label" for="id_test_field_1">Yes</label>
13+
</div>
14+
<div class="tbxforms-radios__item">
15+
<input type="radio"
16+
name="test_field"
17+
class="tbxforms-radios__input"
18+
id="id_test_field_2"
19+
value="no" />
20+
<label class="tbxforms-label tbxforms-radios__label" for="id_test_field_2">No</label>
21+
</div>
22+
</div>
23+
</fieldset>
24+
</div>
25+
</form>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<form class="tbxforms" method="post">
2+
<div id="div_id_test_field" class="tbxforms-form-group">
3+
<label for="id_test_field" class="tbxforms-label tbxforms-label--m">Test field</label>
4+
<select name="test_field" class="tbxforms-select" id="id_test_field">
5+
<option value="yes">Yes</option>
6+
<option value="no">No</option>
7+
</select>
8+
</div>
9+
</form>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<form class="tbxforms" method="post">
2+
<div id="div_id_test_field" class="tbxforms-form-group">
3+
<label for="id_test_field" class="tbxforms-label tbxforms-label--m">Test field</label>
4+
<input type="text"
5+
name="test_field"
6+
class="tbxforms-input tbxforms-input--text"
7+
id="id_test_field">
8+
</div>
9+
</form>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<form class="tbxforms" method="post">
2+
<div id="div_id_test_field" class="tbxforms-form-group">
3+
<label for="id_test_field" class="tbxforms-label tbxforms-label--m">Test field</label>
4+
<input type="text"
5+
name="test_field"
6+
rows="10"
7+
class="tbxforms-input tbxforms-input--text"
8+
id="id_test_field">
9+
</div>
10+
</form>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<form class="tbxforms" method="post">
2+
<div class="tbxforms-form-group"
3+
data-conditional-field-name="trigger_field"
4+
data-conditional-field-values="[&quot;yes&quot;]">
5+
<div id="div_id_field1" class="tbxforms-form-group">
6+
<label for="id_field1" class="tbxforms-label tbxforms-label--m">Field1</label>
7+
<input type="text"
8+
name="field1"
9+
class="tbxforms-input tbxforms-input--text"
10+
required="required"
11+
id="id_field1">
12+
</div>
13+
<div id="div_id_field2" class="tbxforms-form-group">
14+
<label for="id_field2" class="tbxforms-label tbxforms-label--m">Field2</label>
15+
<input type="text"
16+
name="field2"
17+
class="tbxforms-input tbxforms-input--text"
18+
required="required"
19+
id="id_field2">
20+
</div>
21+
</div>
22+
</form>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<form class="tbxforms" method="post">
2+
<fieldset class="tbxforms-form-group tbxforms-fieldset"
3+
data-conditional-field-name="trigger_field"
4+
data-conditional-field-values="[&quot;yes&quot;]">
5+
<div id="div_id_field1" class="tbxforms-form-group">
6+
<label for="id_field1" class="tbxforms-label tbxforms-label--m">Field1</label>
7+
<input type="text"
8+
name="field1"
9+
class="tbxforms-input tbxforms-input--text"
10+
required="required"
11+
id="id_field1">
12+
</div>
13+
<div id="div_id_field2" class="tbxforms-form-group">
14+
<label for="id_field2" class="tbxforms-label tbxforms-label--m">Field2</label>
15+
<input type="text"
16+
name="field2"
17+
class="tbxforms-input tbxforms-input--text"
18+
required="required"
19+
id="id_field2">
20+
</div>
21+
</fieldset>
22+
</form>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<form class="tbxforms" method="post">
2+
<div class="tbxforms-form-group"
3+
data-conditional-field-name="trigger_field"
4+
data-conditional-field-values="[&quot;yes&quot;, &quot;maybe&quot;]">
5+
<div id="div_id_field1" class="tbxforms-form-group">
6+
<label for="id_field1" class="tbxforms-label tbxforms-label--m">Field1</label>
7+
<input type="text"
8+
name="field1"
9+
class="tbxforms-input tbxforms-input--text"
10+
required="required"
11+
id="id_field1">
12+
</div>
13+
</div>
14+
</form>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<form class="tbxforms" method="post">
2+
<fieldset class="tbxforms-form-group tbxforms-fieldset"
3+
data-conditional-field-name="trigger_field"
4+
data-conditional-field-values="[&quot;yes&quot;, &quot;maybe&quot;]">
5+
<div id="div_id_field1" class="tbxforms-form-group">
6+
<label for="id_field1" class="tbxforms-label tbxforms-label--m">Field1</label>
7+
<input type="text"
8+
name="field1"
9+
class="tbxforms-input tbxforms-input--text"
10+
required="required"
11+
id="id_field1">
12+
</div>
13+
</fieldset>
14+
</form>

0 commit comments

Comments
 (0)