Skip to content

Commit

Permalink
Split choice from enum column info (#111)
Browse files Browse the repository at this point in the history
  • Loading branch information
shosca committed Jan 4, 2019
1 parent 39f7a7f commit 7cac0d9
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 29 deletions.
69 changes: 46 additions & 23 deletions django_sorcery/db/meta/column.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from __future__ import absolute_import, print_function, unicode_literals
import datetime
import decimal
import enum
import warnings

import six
Expand Down Expand Up @@ -60,6 +61,14 @@ def __new__(cls, *args, **kwargs):
for base in column.type.__class__.mro():
if base in column_info_mapping:
override_cls = column_info_mapping.get(base, cls)

enum_class = getattr(column.type, "enum_class", object) or object
for sub in enum_class.mro():
if (base, sub) in column_info_mapping:
override_cls = column_info_mapping.get((base, sub), cls)
break

if override_cls:
break

cls = override_cls or cls
Expand Down Expand Up @@ -203,25 +212,11 @@ def widget(self):
return super(text_column_info, self).widget or djangoforms.Textarea


class enum_column_info(column_info):
@property
def form_class(self):
form_class = super(enum_column_info, self).form_class
if form_class:
return form_class

return (
djangofields.TypedChoiceField if isinstance(self.choices, (list, set, tuple)) else sorceryfields.EnumField
)

class base_choice_info(column_info):
@property
def field_kwargs(self):
kwargs = super(enum_column_info, self).field_kwargs

if isinstance(self.choices, (list, set, tuple)):
kwargs["choices"] = [(x, x) for x in self.choices]
else:
kwargs["choices"] = self.choices
kwargs = super(base_choice_info, self).field_kwargs
kwargs["choices"] = self.form_choices

# Many of the subclass-specific formfield arguments (min_value,
# max_value) don't apply for choice fields, so be sure to only pass
Expand All @@ -246,14 +241,41 @@ def field_kwargs(self):

return kwargs


class choice_column_info(base_choice_info):
default_form_class = djangofields.TypedChoiceField

@property
def form_choices(self):
return [(x, x) for x in self.choices]

def to_python(self, value):
if value is None:
return value
if isinstance(self.choices, (list, set, tuple)):

with suppress(TypeError, ValueError):
parsed = type(next(iter(self.choices)))(value)
if parsed not in self.choices:
raise ValidationError("%(value)r is not a valid choice.", code="invalid", params={"value": str(value)})
return parsed
if parsed in self.choices:
return parsed

parsed = six.text_type(value).strip()
parsed = self.coercer.to_python(parsed)
if parsed in self.coercer.empty_values:
return None

raise ValidationError("%(value)r is not a valid choice.", code="invalid", params={"value": str(value)})


class enum_column_info(base_choice_info):
default_form_class = sorceryfields.EnumField

@property
def form_choices(self):
return self.choices

def to_python(self, value):
if value is None:
return value

with suppress(TypeError, KeyError, ValueError):
return self.choices[value]
Expand All @@ -262,7 +284,7 @@ def to_python(self, value):
with suppress(TypeError, AttributeError):
return getattr(self.choices, value)

raise ValidationError("%(value)r is not a valid choice.", code="invalid", params={"value": str(value)})
return self.coercer.to_python(value)


class numeric_column_info(column_info):
Expand Down Expand Up @@ -429,7 +451,8 @@ def to_python(self, value):


COLUMN_INFO_MAPPING = {
sa.sql.sqltypes.Enum: enum_column_info,
(sa.sql.sqltypes.Enum, enum.Enum): enum_column_info,
(sa.sql.sqltypes.Enum, object): choice_column_info,
sa.sql.sqltypes.String: string_column_info,
sa.sql.sqltypes.Text: text_column_info,
sa.sql.sqltypes.Numeric: numeric_column_info,
Expand Down
13 changes: 10 additions & 3 deletions tests/db/meta/test_column.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ def test_choice_enum(self):
col.field_kwargs,
)
self.assertEqual(col.parent_model, Vehicle)
self.assertEqual(repr(col), "<enum_column_info(Vehicle.paint)>")
self.assertEqual(repr(col), "<choice_column_info(Vehicle.paint)>")

def test_plain_sqla(self):
info = meta.model_info(AllKindsOfFields)
Expand Down Expand Up @@ -198,7 +198,7 @@ def test_formfield(self):

def test_clean(self):
info = meta.column_info(sa.Column(sa.Enum(*["1", "2", "3"])), name="test")
tests = [(None, None), ("", ValidationError), ("1", "1"), (1, "1"), ("4", ValidationError)]
tests = [(None, None), ("", None), ("1", "1"), (1, "1"), ("4", ValidationError), (5, ValidationError)]
_run_tests(self, info, tests)


Expand All @@ -218,7 +218,14 @@ class Demo(enum.Enum):
two = "2"

info = meta.column_info(sa.Column(sa.Enum(Demo)), name="test")
tests = [(None, None), ("one", Demo.one), ("1", Demo.one), (Demo.one, Demo.one), (1, ValidationError)]
tests = [
(None, None),
("", None),
("one", Demo.one),
("1", Demo.one),
(Demo.one, Demo.one),
(1, ValidationError),
]
_run_tests(self, info, tests)


Expand Down
4 changes: 2 additions & 2 deletions tests/db/meta/test_composite.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def test_full_clean(self):
{
"location": ["Primary key is required when other location is provided."],
"other_location": {
"state": ["'ny' is not a valid choice."],
"state": ["Select a valid choice. ny is not one of the available choices."],
"street": ["Street should be at least 2 characters."],
"zip": ["Zip cannot start with 0."],
},
Expand All @@ -76,7 +76,7 @@ def test_full_clean_exclude_inner_composite_fields(self):
"location": ["Primary key is required when other location is provided."],
"other_location": {
"street": ["Street should be at least 2 characters."],
"state": ["'ny' is not a valid choice."],
"state": ["Select a valid choice. ny is not one of the available choices."],
},
},
)
Expand Down
2 changes: 1 addition & 1 deletion tests/db/meta/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def test_reprs(self):
" <boolean_column_info(Vehicle.is_used)>",
" <numeric_column_info(Vehicle.msrp)>",
" <string_column_info(Vehicle.name)>",
" <enum_column_info(Vehicle.paint)>",
" <choice_column_info(Vehicle.paint)>",
" <enum_column_info(Vehicle.type)>",
" <relation_info(Vehicle.options)>",
" <relation_info(Vehicle.owner)>",
Expand Down

0 comments on commit 7cac0d9

Please sign in to comment.