Skip to content

Commit

Permalink
Fixes #111
Browse files Browse the repository at this point in the history
  • Loading branch information
robinedwards committed Oct 21, 2014
1 parent f9a1498 commit dee7ca0
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 1 deletion.
3 changes: 3 additions & 0 deletions Changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
Version 1.0.3 unreleased
* add support for choices on string properites.

Version 1.0.2 2014-10-21
* updated documentation
* sphinx and rtd
Expand Down
18 changes: 18 additions & 0 deletions doc/source/properties.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,24 @@ You may provide arguments using a wrapper function or lambda::

my_datetime = DateTimeProperty(default=lambda: datetime.now(pytz.utc))

Choices
=======

You can specify a list of valid values for a `StringProperty` using choices::

class Person(StructuredNode):
SEXES = (
('M', 'Male'),
('F', 'Female')
)
sex = StringProperty(required=True, choices=SEXES)

tim = Person(sex='M').save()
tim.sex # M
tim.get_sex_display() # 'Male'

The value will be checked both when saved and loaded from neo4j.

Dates and times
===============

Expand Down
28 changes: 27 additions & 1 deletion neomodel/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
if sys.version_info >= (3, 0):
unicode = lambda x: str(x)

def display_for(key):
def display_choice(self):
return getattr(self.__class__, key).choice_map[getattr(self, key)]
return display_choice

class PropertyManager(object):
"""Common stuff for handling properties in nodes and relationships"""
Expand All @@ -27,6 +31,10 @@ def __init__(self, *args, **kwargs):
else:
setattr(self, key, kwargs[key])

if hasattr(val, 'choices') and getattr(val, 'choices'):
setattr(self, 'get_{}_display'.format(key),
types.MethodType(display_for(key), self))

if key in kwargs:
del kwargs[key]

Expand Down Expand Up @@ -97,7 +105,7 @@ def validator(self, value, obj=None):


class Property(object):
def __init__(self, unique_index=False, index=False, required=False, default=None):
def __init__(self, unique_index=False, index=False, required=False, default=None, **kwargs):
if default and required:
raise Exception("required and default are mutually exclusive")

Expand Down Expand Up @@ -125,12 +133,30 @@ def is_indexed(self):


class StringProperty(Property):
def __init__(self, **kwargs):
super(StringProperty, self).__init__(**kwargs)
self.choices = kwargs.get('choices', None)

if self.choices:
if not isinstance(self.choices, tuple):
raise ValueError("Choices must be a tuple of tuples")

self.choice_map = dict(self.choices)

@validator
def inflate(self, value):
if self.choices and value not in self.choice_map:
raise ValueError("Invalid choice {} not in {}".format(
value, ', '.join(self.choice_map.keys())))

return unicode(value)

@validator
def deflate(self, value):
if self.choices and value not in self.choice_map:
raise ValueError("Invalid choice {} not in {}".format(
value, ','.join(self.choice_map.keys())))

return unicode(value)

def default_value(self):
Expand Down
17 changes: 17 additions & 0 deletions test/test_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,23 @@ class FooBar(object):
pass


def test_string_property_w_choice():
class TestChoices(StructuredNode):
SEXES = (('M', 'Male'), ('F', 'Female'))
sex = StringProperty(required=True, choices=SEXES)

try:
TestChoices(sex='Z').save()
except DeflateError as e:
assert True
assert str(e).index('choice')
else:
assert False

node = TestChoices(sex='M').save()
assert node.get_sex_display() == 'Male'


def test_deflate_inflate():
prop = IntegerProperty(required=True)
prop.name = 'age'
Expand Down

0 comments on commit dee7ca0

Please sign in to comment.