Skip to content

Commit d57a512

Browse files
authored
Merge pull request #177 from novafloss/confirmation-preset-tests
Confirmation preset tests
2 parents 23031be + eb947a0 commit d57a512

File tree

5 files changed

+165
-38
lines changed

5 files changed

+165
-38
lines changed

demo/tests/test_end_point.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,18 @@ class CreateSerializerTestCase(TestCase):
528528
}],
529529
}]
530530

531+
invalid_presets = [{
532+
'preset_id': 'unknown',
533+
'message': 'not the same',
534+
'arguments': [{
535+
'slug': 'left',
536+
'field_id': 'input-date',
537+
}, {
538+
'slug': 'right',
539+
'field_id': 'text_input',
540+
}],
541+
}]
542+
531543
presets_with_wrong_parameters = [{
532544
'preset_id': 'confirmation',
533545
'message': 'noteq!',
@@ -601,6 +613,17 @@ def test_create_form_presets(self):
601613
slug='right', field_id='text_input').exists()
602614
)
603615

616+
def test_create_form_presets_invalid_preset(self):
617+
data = copy.deepcopy(self.data)
618+
data['fields'] = copy.deepcopy(self.fields_with_validation)
619+
data['presets'] = copy.deepcopy(self.invalid_presets)
620+
serializer = FormidableSerializer(data=data)
621+
self.assertFalse(serializer.is_valid(), serializer.errors)
622+
self.assertEqual(
623+
serializer.errors['presets'][0]['preset_id'][0],
624+
'unknown is not an available preset' # noqa
625+
)
626+
604627
def test_create_form_with_presets_invalid_argument(self):
605628
data = copy.deepcopy(self.data)
606629
data['presets'] = copy.deepcopy(self.presets_with_wrong_parameters)
@@ -1232,7 +1255,10 @@ def test_render_preset_hybrid_arg(self):
12321255
self.assertIn('field', data['types'])
12331256

12341257
def test_render_preset_with_argument(self):
1235-
preset_instance = self.PresetsTestWithArgs(arguments=[])
1258+
preset_instance = self.PresetsTestWithArgs(arguments=[
1259+
PresetArg(slug='lhs', field_id='foo'),
1260+
PresetArg(slug='rhs', value='toto'),
1261+
])
12361262
serializer = PresetsSerializer(preset_instance)
12371263
self.assertTrue(serializer.data)
12381264
data = serializer.data

demo/tests/test_forms.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,75 @@ def test_from_json_raised_error(self):
781781

782782
class TestInnerPresets(TestCase):
783783

784+
def test_confirmation_fields(self):
785+
class TestPresets(FormidableForm):
786+
left = fields.IntegerField()
787+
right = fields.IntegerField()
788+
789+
class Meta:
790+
presets = [
791+
ConfirmationPresets(
792+
[PresetArg(slug='left', field_id='left'),
793+
PresetArg(slug='right', field_id='right')],
794+
),
795+
]
796+
797+
formidable = TestPresets.to_formidable(label='presets')
798+
form_class = formidable.get_django_form_class()
799+
form = form_class(data={'left': 42, 'right': 42})
800+
self.assertTrue(form.is_valid())
801+
form = form_class(data={'left': 42, 'right': "42"})
802+
self.assertTrue(form.is_valid())
803+
form = form_class(data={'left': 42, 'right': 21})
804+
self.assertFalse(form.is_valid())
805+
form = form_class(data={'left': 42, 'right': "21"})
806+
self.assertFalse(form.is_valid())
807+
808+
def test_confirmation_value(self):
809+
class TestPresets(FormidableForm):
810+
number = fields.IntegerField()
811+
812+
class Meta:
813+
presets = [
814+
ConfirmationPresets(
815+
[PresetArg(slug='left', field_id='number'),
816+
PresetArg(slug='right', value='42')],
817+
),
818+
]
819+
820+
formidable = TestPresets.to_formidable(label='presets')
821+
form_class = formidable.get_django_form_class()
822+
form = form_class(data={'number': 33})
823+
self.assertFalse(form.is_valid())
824+
self.assertEqual(
825+
form.errors,
826+
{'__all__': ['33 is not equal to 42']}
827+
)
828+
829+
form = form_class(data={'number': 42})
830+
# 42 should equal 42...
831+
self.assertTrue(form.is_valid(), form.errors)
832+
833+
def test_confirmation_access(self):
834+
class TestPresets(FormidableForm):
835+
number = fields.IntegerField(accesses={
836+
'jedi': HIDDEN,
837+
})
838+
839+
class Meta:
840+
presets = [
841+
ConfirmationPresets(
842+
[PresetArg(slug='left', field_id='number'),
843+
PresetArg(slug='right', value='42')],
844+
),
845+
]
846+
847+
formidable = TestPresets.to_formidable(label='presets')
848+
form_class = formidable.get_django_form_class(role='jedi')
849+
form = form_class(data={'number': 33})
850+
self.assertTrue(form.is_valid())
851+
self.assertEqual(len(form.rules), 0)
852+
784853
def test_confirmation_not_required_field(self):
785854
class TestPresets(FormidableForm):
786855
number = fields.IntegerField(accesses={

demo/tests/test_presets.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,19 +55,15 @@ def test_missing_left_operand(self):
5555
args = [
5656
PresetArg(slug='right', field_id='confirm_password')
5757
]
58-
rule = ConfirmationPresets(args)
59-
cleaned_data = {'password': 'toto', 'confirm_password': 'tutu'}
6058
with self.assertRaises(ImproperlyConfigured):
61-
self.assertTrue(rule(cleaned_data))
59+
ConfirmationPresets(args)
6260

6361
def test_missing_right_operand(self):
6462
args = [
6563
PresetArg(slug='left', field_id='password'),
6664
]
67-
rule = ConfirmationPresets(args)
68-
cleaned_data = {'password': 'toto', 'confirm_password': 'tutu'}
6965
with self.assertRaises(ImproperlyConfigured):
70-
self.assertTrue(rule(cleaned_data))
66+
ConfirmationPresets(args)
7167

7268
def test_comparison_eq_ok(self):
7369
args = [

formidable/forms/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def get_dynamic_form_class(formidable, role=None, field_factory=None):
100100
else:
101101
attrs[field.slug] = form_field
102102

103-
attrs['rules'] = presets_register.build_rules(formidable)
103+
attrs['rules'] = presets_register.build_rules(formidable, attrs)
104104
return type(str('DynamicForm'), (BaseDynamicForm,), attrs)
105105

106106

formidable/forms/validations/presets.py

Lines changed: 66 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
from collections import OrderedDict
66

7-
from django.core.exceptions import ImproperlyConfigured, ValidationError
7+
from django.core.exceptions import (
8+
FieldError, ImproperlyConfigured, ValidationError
9+
)
810
from django.utils.translation import ugettext_lazy as _
911

1012
import six
@@ -14,13 +16,19 @@
1416

1517
class PresetsRegister(dict):
1618

17-
def build_rules(self, form):
18-
return list(self.gen_rules(form))
19+
def build_rules(self, form, fields):
20+
return list(self.gen_rules(form, fields))
1921

20-
def gen_rules(self, form):
22+
def gen_rules(self, form, fields):
2123
for preset in form.presets.all():
2224
klass = self[preset.slug]
23-
yield klass(preset.arguments.all(), message=preset.message)
25+
try:
26+
yield klass(preset.arguments.all(),
27+
fields=fields, message=preset.message)
28+
except FieldError:
29+
# preset defined on a field that is filtered (current role
30+
# does not match the accesses)
31+
pass
2432

2533

2634
presets_register = PresetsRegister()
@@ -71,7 +79,7 @@ def __new__(mcls, name, base, attrs):
7179

7280
class PresetArgument(object):
7381

74-
def __init__(self, label, slug=None, order=None,
82+
def __init__(self, label, slug=None, order=None, cast_value_with=None,
7583
help_text='', placeholder='', items=None):
7684
self.slug = slug
7785
self.label = label
@@ -81,6 +89,7 @@ def __init__(self, label, slug=None, order=None,
8189
self.has_items = items is not None
8290
self.items = items or {}
8391
self.order = order
92+
self.cast_value_with = cast_value_with
8493

8594
def get_types(self):
8695
return [self.__class__.type_]
@@ -92,29 +101,18 @@ def set_slug(self, slug):
92101
if self.slug is None:
93102
self.slug = slug
94103

95-
def get_value(self, arguments, fields):
96-
97-
for arg in arguments:
98-
if arg.slug == self.slug:
99-
if arg.field_id:
100-
return fields[arg.field_id]
101-
return arg.value
102-
103-
raise ImproperlyConfigured(
104-
_('{slug} is missing').format(slug=self.slug)
105-
)
104+
def get_value(self, arguments, data):
105+
arg = arguments[self.slug]
106+
if arg.field_id:
107+
return data[arg.field_id]
108+
return arg.value
106109

107110
def to_formidable(self, preset, arguments):
108111

109-
for arg in arguments:
110-
if arg.slug == self.slug:
111-
arg.preset = preset
112-
arg.save()
113-
return arg
114-
115-
raise ImproperlyConfigured(
116-
'{slug} is missing'.format(slug=self.slug)
117-
)
112+
arg = arguments[self.slug]
113+
arg.preset = preset
114+
arg.save()
115+
return arg
118116

119117

120118
class PresetFieldArgument(PresetArgument):
@@ -143,16 +141,53 @@ class Presets(six.with_metaclass(PresetsMetaClass)):
143141
class MetaParameters:
144142
pass
145143

146-
def __init__(self, arguments, message=None):
147-
self.arguments = arguments
144+
def __init__(self, arguments, fields=None, message=None):
148145
self.message = message or self.default_message
146+
# Check if `arguments` match `self._declared_arguments`
147+
decl_args_set = set(self._declared_arguments.keys())
148+
args_set = {a.slug for a in arguments}
149+
missing_args = decl_args_set - args_set
150+
if missing_args:
151+
raise ImproperlyConfigured(
152+
_('Missing presets arguments : {}').format(
153+
', '.join(missing_args))
154+
)
155+
extra_args = args_set - decl_args_set
156+
if extra_args:
157+
raise ImproperlyConfigured(
158+
_('Extra presets arguments : {}').format(
159+
', '.join(extra_args))
160+
)
161+
self.arguments = {arg.slug: arg for arg in arguments}
162+
163+
if fields is not None:
164+
# Check if `arguments` references to fields are valid
165+
required_fields = {
166+
a.field_id
167+
for a in self.arguments.values()
168+
if a.field_id
169+
}
170+
missing_fields = required_fields - set(fields.keys())
171+
if missing_fields:
172+
raise FieldError(
173+
_('Bad field references in presets : {}').format(
174+
', '.join(missing_fields))
175+
)
176+
# Value conversions
177+
for arg_name, arg in self._declared_arguments.items():
178+
instance = self.arguments[arg_name]
179+
if arg.cast_value_with and instance.value is not None:
180+
field_name = self.arguments[arg.cast_value_with].field_id
181+
reference_field = fields[field_name]
182+
instance.value = reference_field.to_python(instance.value)
149183

150184
def has_empty_fields(self, cleaned_data):
151185
def is_empty_value(data):
152186
return (data is None or
153187
(isinstance(data, six.string_types) and not data))
154188

155-
used_fields = {a.field_id for a in self.arguments if a.field_id}
189+
used_fields = {a.field_id
190+
for a in self.arguments.values() if a.field_id}
156191
# we do not filter out required fields because they can't be empty
157192
return any(
158193
is_empty_value(cleaned_data.get(name, None))
@@ -202,7 +237,8 @@ class MetaParameters:
202237
_('Reference'), help_text=_('field to compare'), order=0
203238
)
204239
right = PresetFieldOrValueArgument(
205-
_('Compare to'), help_text=_('compare with'), order=1
240+
_('Compare to'), help_text=_('compare with'), order=1,
241+
cast_value_with='left'
206242
)
207243

208244
def run(self, left, right):

0 commit comments

Comments
 (0)