Skip to content

Commit 0a28f93

Browse files
committed
Use new tri.declarative major
1 parent 4b67ee3 commit 0a28f93

File tree

5 files changed

+40
-120
lines changed

5 files changed

+40
-120
lines changed

HISTORY.rst

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
11
Changelog
22
---------
33

4+
6.0.0 (2019-04-12)
5+
~~~~~~~~~~~~~~~~~~
6+
7+
* Make `Field` shortcuts compatible with subclassing. Previous fix didn't work all the way.
8+
9+
* Use the new major tri.declarative, and update to follow the new style of class member shortcuts
10+
11+
* Major fixes to how `from_model` works. Subclassing `Form` and `Field` now works like you'd expect. This is a breaking change.
12+
13+
* Removed `Field.comma_separated` shortcut. This was never used, and poorly tested.
14+
415
5.4.0 (2019-04-01)
516
~~~~~~~~~~~~~~~~~~
617

7-
* Make Field shortcuts compatible with subclassing
18+
* Make `Field` shortcuts compatible with subclassing
819

920

1021
5.3.1 (2019-03-20)

lib/tri/form/__init__.py

Lines changed: 10 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ def member_from_model(cls, model, factory_lookup, defaults_factory, factory_look
181181
factory_lookup=factory_lookup,
182182
defaults_factory=defaults_factory,
183183
factory_lookup_register_function=factory_lookup_register_function,
184-
field_name=sub_field_name,
184+
field_name=field_path_rest,
185185
**kwargs)
186186
result.name = field_name
187187
result.attr = field_name
@@ -191,6 +191,7 @@ def member_from_model(cls, model, factory_lookup, defaults_factory, factory_look
191191
kwargs,
192192
defaults_factory(model_field),
193193
name=field_name,
194+
call_target__cls=cls,
194195
)
195196

196197
factory = factory_lookup.get(type(model_field), MISSING)
@@ -207,16 +208,9 @@ def member_from_model(cls, model, factory_lookup, defaults_factory, factory_look
207208
message += ' Register a factory with %s, you can also register one that returns None to not handle this field type' % factory_lookup_register_function.__name__
208209
raise AssertionError(message)
209210

210-
def call_target(*call_target_args, **call_target_kwargs):
211-
class_call_target = call_target_kwargs.pop('class_call_target', None)
212-
if class_call_target:
213-
return getattr(cls, class_call_target)(*call_target_args, **call_target_kwargs)
214-
else:
215-
return cls(*call_target_args, **call_target_kwargs)
216-
217211
if factory:
218212
factory = evaluate(factory, model_field=model_field, field_name=field_name)
219-
return factory(call_target=call_target, model_field=model_field, model=model, **kwargs) if factory else None
213+
return factory(model_field=model_field, model=model, **kwargs) if factory is not None else None
220214

221215

222216
@dispatch(
@@ -967,7 +961,7 @@ def _choice_post_validation(form, field):
967961

968962
@classmethod
969963
@class_shortcut(
970-
class_call_target="choice",
964+
call_target__attribute="choice",
971965
choices=[True, False],
972966
parse=choice_parse,
973967
choice_to_option=lambda form, field, choice, **_: (
@@ -983,7 +977,7 @@ def boolean_tristate(cls, call_target=None, **kwargs):
983977

984978
@classmethod
985979
@class_shortcut(
986-
class_call_target="choice",
980+
call_target__attribute="choice",
987981
parse=choice_queryset_parse,
988982
choice_to_option=choice_queryset_choice_to_option,
989983
endpoint_path=choice_queryset_endpoint_path,
@@ -1011,7 +1005,7 @@ def choice_queryset(cls, choices, call_target=None, **kwargs):
10111005

10121006
@classmethod
10131007
@class_shortcut(
1014-
class_call_target="choice",
1008+
call_target__attribute="choice",
10151009
attrs__multiple=True,
10161010
choice_to_option=multi_choice_choice_to_option,
10171011
is_list=True,
@@ -1021,7 +1015,7 @@ def multi_choice(cls, call_target=None, **kwargs):
10211015

10221016
@classmethod
10231017
@class_shortcut(
1024-
class_call_target="choice_queryset",
1018+
call_target__attribute="choice_queryset",
10251019
attrs__multiple=True,
10261020
choice_to_option=multi_choice_queryset_choice_to_option,
10271021
is_list=True,
@@ -1031,7 +1025,7 @@ def multi_choice_queryset(cls, call_target=None, **kwargs):
10311025

10321026
@classmethod
10331027
@class_shortcut(
1034-
class_call_target="choice",
1028+
call_target__attribute="choice",
10351029
input_template='tri_form/radio.html',
10361030
)
10371031
def radio(cls, call_target=None, **kwargs):
@@ -1131,7 +1125,7 @@ def phone_number(cls, call_target=None, **kwargs):
11311125

11321126
@classmethod
11331127
@class_shortcut(
1134-
class_call_target='choice_queryset',
1128+
call_target__attribute='choice_queryset',
11351129
)
11361130
def foreign_key(cls, model_field, model, call_target, **kwargs):
11371131
del model
@@ -1143,7 +1137,7 @@ def foreign_key(cls, model_field, model, call_target, **kwargs):
11431137

11441138
@classmethod
11451139
@class_shortcut(
1146-
class_call_target='multi_choice_queryset',
1140+
call_target__attribute='multi_choice_queryset',
11471141
)
11481142
def many_to_many(cls, call_target, model_field, **kwargs):
11491143
setdefaults_path(
@@ -1156,50 +1150,6 @@ def many_to_many(cls, call_target, model_field, **kwargs):
11561150
kwargs['model'] = model_field.remote_field.model
11571151
return call_target(model_field=model_field, **kwargs)
11581152

1159-
@classmethod
1160-
@class_shortcut(
1161-
nested=EMPTY,
1162-
)
1163-
def comma_separated(cls, call_target, nested, **kwargs):
1164-
"""
1165-
Shortcut to create a comma separated list of something. You can use this to create a comma separated text input that gives nice validation errors easily. Example:
1166-
1167-
.. code:: python
1168-
1169-
Field.comma_separated(nested=Field.email)
1170-
"""
1171-
if 'call_target' in nested:
1172-
nested = nested()
1173-
else:
1174-
nested = nested(class_call_target_class=cls)
1175-
1176-
def parse_comma_separated(form, field, string_value):
1177-
errors = []
1178-
result = []
1179-
for x in string_value.split(','):
1180-
x = x.strip()
1181-
try:
1182-
result.append(nested.parse(form=form, field=field, string_value=x.strip()))
1183-
except ValueError as e:
1184-
errors.append('Invalid value "%s": %s' % (x, e))
1185-
except ValidationError as e:
1186-
for message in e.messages:
1187-
errors.append('Invalid value "%s": %s' % (x, message))
1188-
if errors:
1189-
raise ValidationError(errors)
1190-
return ', '.join(result)
1191-
1192-
def is_valid_comma_separated(form, field, parsed_data):
1193-
errors = set()
1194-
for x in parsed_data.split(','):
1195-
x = x.strip()
1196-
is_valid, error = nested.is_valid(form=form, field=field, parsed_data=x)
1197-
if not is_valid:
1198-
errors.add('Invalid value "%s": %s' % (x, error))
1199-
return errors == set(), errors
1200-
1201-
return call_target(is_valid=is_valid_comma_separated, parse=parse_comma_separated, **kwargs)
1202-
12031153

12041154
def get_fields(model):
12051155
# noinspection PyProtectedMember

lib/tri/form/compat.py

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,38 +29,34 @@ def get_template_from_string(template_code, origin=None, name=None):
2929
def setup_db_compat():
3030
from tri.form import register_field_factory
3131
from django.db.models import IntegerField, FloatField, TextField, BooleanField, AutoField, CharField, \
32-
CommaSeparatedIntegerField, DateField, DateTimeField, DecimalField, EmailField, URLField, TimeField, \
32+
DateField, DateTimeField, DecimalField, EmailField, URLField, TimeField, \
3333
ForeignKey, ManyToManyField, FileField, ManyToOneRel, ManyToManyRel
3434

3535
# The order here is significant because of inheritance structure. More specific must be below less specific.
36-
register_field_factory(CharField, Shortcut(class_call_target=''))
37-
register_field_factory(URLField, Shortcut(class_call_target='url'))
38-
register_field_factory(TimeField, Shortcut(class_call_target='time'))
39-
register_field_factory(EmailField, Shortcut(class_call_target='email'))
40-
register_field_factory(DecimalField, Shortcut(class_call_target='decimal'))
41-
register_field_factory(DateField, Shortcut(class_call_target='date'))
42-
register_field_factory(DateTimeField, Shortcut(class_call_target='datetime'))
43-
register_field_factory(
44-
CommaSeparatedIntegerField,
45-
Shortcut(class_call_target='comma_separated', nested=Shortcut(class_call_target='integer')),
46-
)
36+
register_field_factory(CharField, Shortcut())
37+
register_field_factory(URLField, Shortcut(call_target__attribute='url'))
38+
register_field_factory(TimeField, Shortcut(call_target__attribute='time'))
39+
register_field_factory(EmailField, Shortcut(call_target__attribute='email'))
40+
register_field_factory(DecimalField, Shortcut(call_target__attribute='decimal'))
41+
register_field_factory(DateField, Shortcut(call_target__attribute='date'))
42+
register_field_factory(DateTimeField, Shortcut(call_target__attribute='datetime'))
4743
register_field_factory(
4844
BooleanField,
4945
lambda model_field, **kwargs: (
50-
Shortcut(class_call_target='boolean')
46+
Shortcut(call_target__attribute='boolean')
5147
if not model_field.null
52-
else Shortcut(class_call_target='boolean_tristate')
48+
else Shortcut(call_target__attribute='boolean_tristate')
5349
)
5450
)
55-
register_field_factory(TextField, Shortcut(class_call_target='text'))
56-
register_field_factory(FloatField, Shortcut(class_call_target='float'))
57-
register_field_factory(IntegerField, Shortcut(class_call_target='integer'))
58-
register_field_factory(AutoField, Shortcut(class_call_target='integer', show=False))
51+
register_field_factory(TextField, Shortcut(call_target__attribute='text'))
52+
register_field_factory(FloatField, Shortcut(call_target__attribute='float'))
53+
register_field_factory(IntegerField, Shortcut(call_target__attribute='integer'))
54+
register_field_factory(AutoField, Shortcut(call_target__attribute='integer', show=False))
5955
register_field_factory(ManyToOneRel, None)
6056
register_field_factory(ManyToManyRel, None)
61-
register_field_factory(FileField, Shortcut(class_call_target='file'))
62-
register_field_factory(ForeignKey, Shortcut(class_call_target='foreign_key'))
63-
register_field_factory(ManyToManyField, Shortcut(class_call_target='many_to_many'))
57+
register_field_factory(FileField, Shortcut(call_target__attribute='file'))
58+
register_field_factory(ForeignKey, Shortcut(call_target__attribute='foreign_key'))
59+
register_field_factory(ManyToManyField, Shortcut(call_target__attribute='many_to_many'))
6460

6561
def field_defaults_factory(model_field):
6662
from tri.form import capitalize

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
tri.declarative >=1.2.0
1+
tri.declarative >=2.0.0,<3.0.0
22
tri.struct >= 2.5.3
33
six

tests/test_forms.py

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -307,43 +307,6 @@ def test_email_field():
307307
assert Form(data=dict(foo='foo@example.com'), fields=[Field.email(name='foo')]).is_valid()
308308

309309

310-
def test_multi_email():
311-
assert Form(data=dict(foo='foo@example.com, foo@example.com'), fields=[Field.comma_separated(nested=Field.email, name='foo')]).is_valid()
312-
313-
314-
def test_comma_separated_errors_on_parse():
315-
def raise_always_value_error(string_value, **_):
316-
raise ValueError('foo %s!' % string_value)
317-
318-
def raise_always_validation_error(string_value, **_):
319-
raise ValidationError(['foo %s!' % string_value, 'bar %s!' % string_value])
320-
321-
assert Form(
322-
data=dict(foo='5, 7'),
323-
fields=[Field.comma_separated(name='foo', nested__parse=raise_always_value_error)]).fields[0].errors == {
324-
u'Invalid value "5": foo 5!',
325-
u'Invalid value "7": foo 7!',
326-
}
327-
328-
assert Form(
329-
data=dict(foo='5, 7'),
330-
fields=[Field.comma_separated(name='foo', nested__parse=raise_always_validation_error)]).fields[0].errors == {
331-
u'Invalid value "5": foo 5!',
332-
u'Invalid value "5": bar 5!',
333-
u'Invalid value "7": foo 7!',
334-
u'Invalid value "7": bar 7!',
335-
}
336-
337-
338-
def test_comma_separated_errors_on_validation():
339-
assert Form(
340-
data=dict(foo='5, 7'),
341-
fields=[Field.comma_separated(name='foo', nested__is_valid=lambda parsed_data, **_: (False, 'foo %s!' % parsed_data))]).fields[0].errors == {
342-
u'Invalid value "5": foo 5!',
343-
u'Invalid value "7": foo 7!',
344-
}
345-
346-
347310
def test_phone_field():
348311
assert Form(data=dict(foo=' asdasd '), fields=[Field.phone_number(name='foo')]).fields[0].errors == {u'Please use format +<country code> (XX) XX XX. Example of US number: +1 (212) 123 4567 or +1 212 123 4567'}
349312
assert Form(data=dict(foo='+1 (212) 123 4567'), fields=[Field.phone_number(name='foo')]).is_valid()

0 commit comments

Comments
 (0)