diff --git a/CHANGES b/CHANGES index f2aedba..62d9248 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +3.2.0 2015-07-20 + + [Jouni Kuusisto ] + * MINOR: Implemented choice labels for IdexableField subclasses. + 3.1.1 2015-03-23 [Anton Berezin ] diff --git a/documentation/schema.rst b/documentation/schema.rst index 47a5660..29e86b1 100644 --- a/documentation/schema.rst +++ b/documentation/schema.rst @@ -1,4 +1,5 @@ .. _schema: +.. py:currentmodule:: resource_api Schema ====== @@ -36,6 +37,18 @@ readonly (bool=False) changeable (bool=False) if True field can be set during creation but cannot be change later on. User's birth date is a valid example. +Field choices +""""""""""""" + +For all fields inherit from :class:`schema.IndexableField`, it's possible to define list of possible +values as choices. When choices are defined for a field field value is only allowed to be set one of these, otherwise +:exc:`errors.ValidationError` is raised. + +To define human readable labels for the value choices, pass a list or tuple of +labels as the *choice_labels* attribute for the field. This is useful for scenarios such as automated UI generation. + +.. autoclass:: resource_api.schema.IndexableField + Primitive fields ---------------- diff --git a/src/resource_api/schema.py b/src/resource_api/schema.py index 7912c4e..523e9cc 100644 --- a/src/resource_api/schema.py +++ b/src/resource_api/schema.py @@ -190,8 +190,21 @@ def get_schema(self): class IndexableField(BaseSimpleField): + """ + Base class for most of the primitive fields. + + choices (list|tuple = None) + Predefined list of possible values for the field. ValidationError will be raised if field value doesn't + match any of the defined values. + choice_labels (list|tuple = None) + List of human readable labels for defined choice values. + The length of the list must match length of the defined choices. + invalid_choices (list|tuple = None) + List of invalid values for the field. ValidationError will be raised if the field value matches any of the + defined values. + """ - def __init__(self, choices=None, invalid_choices=None, **kwargs): + def __init__(self, choices=None, choice_labels=None, invalid_choices=None, **kwargs): super(IndexableField, self).__init__(**kwargs) if choices is not None: @@ -205,6 +218,12 @@ def __init__(self, choices=None, invalid_choices=None, **kwargs): raise DeclarationError("[%d]: %s" % (i, str(e))) choices = tempo + if choice_labels is not None: + if not isinstance(choice_labels, (list, tuple)): + raise DeclarationError("choices has to be a list or tuple") + if choices is None or len(choices) != len(choice_labels): + raise DeclarationError("length of choice_labels has to match with choices.") + if invalid_choices is not None: if not isinstance(invalid_choices, (list, tuple)): raise DeclarationError("invalid_choices has to be a list or tuple") @@ -227,7 +246,7 @@ def __init__(self, choices=None, invalid_choices=None, **kwargs): if inter: raise DeclarationError("these choices are stated as both valid and invalid: %r" % inter) - self.choices, self.invalid_choices = choices, invalid_choices + self.choices, self.choice_labels, self.invalid_choices = choices, choice_labels, invalid_choices def _validate(self, val): super(IndexableField, self)._validate(val) @@ -241,6 +260,7 @@ def _validate(self, val): def get_schema(self): rval = super(IndexableField, self).get_schema() rval["choices"] = self.choices + rval["choice_labels"] = self.choice_labels rval["invalid_choices"] = self.invalid_choices return rval diff --git a/src/tests/resource_schema_test.py b/src/tests/resource_schema_test.py index 6756f0d..247a7f5 100644 --- a/src/tests/resource_schema_test.py +++ b/src/tests/resource_schema_test.py @@ -35,6 +35,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -46,6 +47,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -71,6 +73,7 @@ "default": None, "required": True, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -84,6 +87,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -95,6 +99,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -117,6 +122,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -128,6 +134,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -143,6 +150,7 @@ "default": None, "required": True, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -155,6 +163,7 @@ "default": None, "required": True, "choices": None, + "choice_labels": None, "max_val": None, "pk": True, "invalid_choices": None, @@ -167,6 +176,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -178,6 +188,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -198,6 +209,7 @@ "default": None, "required": True, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -221,6 +233,7 @@ "default": None, "required": True, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -234,6 +247,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -245,6 +259,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -267,6 +282,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -278,6 +294,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -300,6 +317,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -311,6 +329,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -325,6 +344,7 @@ "default": None, "required": True, "choices": None, + "choice_labels": None, "max_val": None, "pk": True, "invalid_choices": None, @@ -337,6 +357,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -348,6 +369,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -371,6 +393,7 @@ "default": None, "required": True, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -394,6 +417,7 @@ "default": None, "required": True, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -407,6 +431,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -418,6 +443,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -440,6 +466,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -451,6 +478,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -473,6 +501,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -484,6 +513,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -498,6 +528,7 @@ "default": None, "required": True, "choices": None, + "choice_labels": None, "max_val": None, "pk": True, "invalid_choices": None, @@ -510,6 +541,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -521,6 +553,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -552,6 +585,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -563,6 +597,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -588,6 +623,7 @@ "default": None, "required": True, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -601,6 +637,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -612,6 +649,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -634,6 +672,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -645,6 +684,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -660,6 +700,7 @@ "default": None, "required": True, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -672,6 +713,7 @@ "default": None, "required": True, "choices": None, + "choice_labels": None, "max_val": None, "pk": True, "invalid_choices": None, @@ -684,6 +726,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" @@ -695,6 +738,7 @@ "default": None, "required": False, "choices": None, + "choice_labels": None, "max_length": None, "invalid_choices": None, "type": "string" diff --git a/src/tests/schema_test.py b/src/tests/schema_test.py index 01d6ade..9db397d 100644 --- a/src/tests/schema_test.py +++ b/src/tests/schema_test.py @@ -163,6 +163,18 @@ def test_wrong_invalid_choices(self): # wrong item types self.assertRaises(DeclarationError, self.field_class, invalid_choices=self.nok_choices) + def test_wrong_choice_labels(self): + # not a list (while len() would match) + self.assertRaises(DeclarationError, self.field_class, choices=self.ok_choices, + choice_labels="W" * len(self.ok_choices)) + # wrong number of labels + self.assertRaises(DeclarationError, self.field_class, choices=self.ok_choices, + choice_labels=self.ok_choices + [self.ok_choices[-1]]) + + def test_ok_choice_labels(self): + self.field_class(choices=self.ok_choices, + choice_labels=self.ok_choices) + def test_not_in_choices(self): field = self.field_class(choices=self.ok_choices) self.assertRaises(ValidationError, field.deserialize, self.nok_choice_value) @@ -291,6 +303,7 @@ def test_get_schema(self): 'description': None, 'required': True, 'choices': None, + 'choice_labels': None, 'invalid_choices': None, 'default': None, 'min_length': 16, @@ -367,8 +380,9 @@ def test_schema(self): "type": "dict", "schema": { "two": {'type': 'list', 'description': None, 'required': True, - 'schema': {'type': 'int', 'description': None, 'choices': None, 'default': None, - 'invalid_choices': None, 'max_val': None, 'min_val': None, 'required': True}}, + 'schema': {'type': 'int', 'description': None, 'choices': None, 'choice_labels': None, + 'default': None, 'invalid_choices': None, 'max_val': None, 'min_val': None, + 'required': True}}, "has_additional_fields": True } }) @@ -386,7 +400,8 @@ def test_dict_schema(self): "schema": { "two": {'type': 'list', 'description': None, 'required': True, 'schema': {'type': 'int', 'description': None, 'choices': None, 'default': None, - 'invalid_choices': None, 'max_val': None, 'min_val': None, 'required': True}}, + 'choice_labels': None, 'invalid_choices': None, 'max_val': None, 'min_val': None, + 'required': True}}, "has_additional_fields": True } })