Merge remote branch 'hmarr/master'
Conflicts: mongoengine/base.py
This commit is contained in:
		| @@ -47,6 +47,40 @@ are as follows: | |||||||
| * :class:`~mongoengine.ReferenceField` | * :class:`~mongoengine.ReferenceField` | ||||||
| * :class:`~mongoengine.GenericReferenceField` | * :class:`~mongoengine.GenericReferenceField` | ||||||
|  |  | ||||||
|  | Field arguments | ||||||
|  | --------------- | ||||||
|  | Each field type can be customized by keyword arguments.  The following keyword  | ||||||
|  | arguments can be set on all fields: | ||||||
|  |  | ||||||
|  | :attr:`db_field` (Default: None) | ||||||
|  |     The MongoDB field name. | ||||||
|  |  | ||||||
|  | :attr:`name` (Default: None) | ||||||
|  |     The mongoengine field name. | ||||||
|  |  | ||||||
|  | :attr:`required` (Default: False) | ||||||
|  |     If set to True and the field is not set on the document instance, a | ||||||
|  |     :class:`~mongoengine.base.ValidationError` will be raised when the document is | ||||||
|  |     validated. | ||||||
|  |  | ||||||
|  | :attr:`default` (Default: None) | ||||||
|  |     A value to use when no value is set for this field. | ||||||
|  |  | ||||||
|  | :attr:`unique` (Default: False) | ||||||
|  |     When True, no documents in the collection will have the same value for this | ||||||
|  |     field. | ||||||
|  |  | ||||||
|  | :attr:`unique_with` (Default: None) | ||||||
|  |     A field name (or list of field names) that when taken together with this | ||||||
|  |     field, will not have two documents in the collection with the same value. | ||||||
|  |  | ||||||
|  | :attr:`primary_key` (Default: False) | ||||||
|  |     When True, use this field as a primary key for the collection. | ||||||
|  |  | ||||||
|  | :attr:`choices` (Default: None) | ||||||
|  |     An iterable of choices to which the value of this field should be limited. | ||||||
|  |      | ||||||
|  |  | ||||||
| List fields | List fields | ||||||
| ----------- | ----------- | ||||||
| MongoDB allows the storage of lists of items. To add a list of items to a | MongoDB allows the storage of lists of items. To add a list of items to a | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ class BaseField(object): | |||||||
|      |      | ||||||
|     def __init__(self, db_field=None, name=None, required=False, default=None,  |     def __init__(self, db_field=None, name=None, required=False, default=None,  | ||||||
|                  unique=False, unique_with=None, primary_key=False, validation=None, |                  unique=False, unique_with=None, primary_key=False, validation=None, | ||||||
|                  create_default=False): |                  create_default=False, choices=None): | ||||||
|         self.db_field = (db_field or name) if not primary_key else '_id' |         self.db_field = (db_field or name) if not primary_key else '_id' | ||||||
|         if name: |         if name: | ||||||
|             import warnings |             import warnings | ||||||
| @@ -39,6 +39,7 @@ class BaseField(object): | |||||||
|         self.primary_key = primary_key |         self.primary_key = primary_key | ||||||
|         self.validation = validation |         self.validation = validation | ||||||
|         self.create_default = create_default |         self.create_default = create_default | ||||||
|  |         self.choices = choices | ||||||
|  |  | ||||||
|     def __get__(self, instance, owner): |     def __get__(self, instance, owner): | ||||||
|         """Descriptor for retrieving a value from a field in a document. Do  |         """Descriptor for retrieving a value from a field in a document. Do  | ||||||
| @@ -84,11 +85,23 @@ class BaseField(object): | |||||||
|     def validate(self, value): |     def validate(self, value): | ||||||
|         """Perform validation on a value. |         """Perform validation on a value. | ||||||
|         """ |         """ | ||||||
|  |         pass | ||||||
|  |  | ||||||
|  |     def _validate(self, value): | ||||||
|  |         # check choices | ||||||
|  |         if self.choices is not None: | ||||||
|  |             if value not in self.choices: | ||||||
|  |                 raise ValidationError("Value must be one of %s."%unicode(self.choices)) | ||||||
|  |          | ||||||
|  |         # check validation argument | ||||||
|         if self.validation is not None: |         if self.validation is not None: | ||||||
|             if (isinstance(self.validation, list) or isinstance(self.validation, tuple)) and value not in self.validation: |             if callable(self.validation): | ||||||
|                 raise ValidationError('Value not in validation list.') |                 if not self.validation(value): | ||||||
|             elif callable(self.validation) and not self.validation(value): |                     raise ValidationError('Value does not match custom validation method.') | ||||||
|                 raise ValidationError('Value does not match custom validation method.') |             else: | ||||||
|  |                 raise ValueError('validation argument must be a callable.') | ||||||
|  |      | ||||||
|  |         self.validate(value) | ||||||
|  |  | ||||||
| class ObjectIdField(BaseField): | class ObjectIdField(BaseField): | ||||||
|     """An field wrapper around MongoDB's ObjectIds. |     """An field wrapper around MongoDB's ObjectIds. | ||||||
| @@ -324,7 +337,7 @@ class BaseDocument(object): | |||||||
|         for field, value in fields: |         for field, value in fields: | ||||||
|             if value is not None: |             if value is not None: | ||||||
|                 try: |                 try: | ||||||
|                     field.validate(value) |                     field._validate(value) | ||||||
|                 except (ValueError, AttributeError, AssertionError), e: |                 except (ValueError, AttributeError, AssertionError), e: | ||||||
|                     raise ValidationError('Invalid value for field of type "' + |                     raise ValidationError('Invalid value for field of type "' + | ||||||
|                                           field.__class__.__name__ + '"') |                                           field.__class__.__name__ + '"') | ||||||
|   | |||||||
| @@ -44,8 +44,6 @@ class StringField(BaseField): | |||||||
|             message = 'String value did not match validation regex' |             message = 'String value did not match validation regex' | ||||||
|             raise ValidationError(message) |             raise ValidationError(message) | ||||||
|  |  | ||||||
|         super(StringField, self).validate(value) |  | ||||||
|  |  | ||||||
|     def lookup_member(self, member_name): |     def lookup_member(self, member_name): | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
| @@ -100,8 +98,6 @@ class URLField(StringField): | |||||||
|                 message = 'This URL appears to be a broken link: %s' % e |                 message = 'This URL appears to be a broken link: %s' % e | ||||||
|                 raise ValidationError(message) |                 raise ValidationError(message) | ||||||
|  |  | ||||||
|         super(URLField, self).validate(value) |  | ||||||
|  |  | ||||||
| class EmailField(StringField): | class EmailField(StringField): | ||||||
|     """A field that validates input as an E-Mail-Address. |     """A field that validates input as an E-Mail-Address. | ||||||
|     """ |     """ | ||||||
| @@ -139,8 +135,6 @@ class IntField(BaseField): | |||||||
|         if self.max_value is not None and value > self.max_value: |         if self.max_value is not None and value > self.max_value: | ||||||
|             raise ValidationError('Integer value is too large') |             raise ValidationError('Integer value is too large') | ||||||
|  |  | ||||||
|         super(IntField, self).validate(value) |  | ||||||
|  |  | ||||||
| class FloatField(BaseField): | class FloatField(BaseField): | ||||||
|     """An floating point number field. |     """An floating point number field. | ||||||
|     """ |     """ | ||||||
| @@ -163,9 +157,6 @@ class FloatField(BaseField): | |||||||
|         if self.max_value is not None and value > self.max_value: |         if self.max_value is not None and value > self.max_value: | ||||||
|             raise ValidationError('Float value is too large') |             raise ValidationError('Float value is too large') | ||||||
|  |  | ||||||
|         super(FloatField, self).validate(value) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class DecimalField(BaseField): | class DecimalField(BaseField): | ||||||
|     """A fixed-point decimal number field. |     """A fixed-point decimal number field. | ||||||
|  |  | ||||||
| @@ -199,9 +190,6 @@ class DecimalField(BaseField): | |||||||
|         if self.max_value is not None and value > self.max_value: |         if self.max_value is not None and value > self.max_value: | ||||||
|             raise ValidationError('Decimal value is too large') |             raise ValidationError('Decimal value is too large') | ||||||
|  |  | ||||||
|         super(DecimalField, self).validate(value) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class BooleanField(BaseField): | class BooleanField(BaseField): | ||||||
|     """A boolean field type. |     """A boolean field type. | ||||||
|  |  | ||||||
| @@ -214,9 +202,6 @@ class BooleanField(BaseField): | |||||||
|     def validate(self, value): |     def validate(self, value): | ||||||
|         assert isinstance(value, bool) |         assert isinstance(value, bool) | ||||||
|  |  | ||||||
|         super(BooleanField, self).validate(value) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class DateTimeField(BaseField): | class DateTimeField(BaseField): | ||||||
|     """A datetime field. |     """A datetime field. | ||||||
|     """ |     """ | ||||||
| @@ -224,9 +209,6 @@ class DateTimeField(BaseField): | |||||||
|     def validate(self, value): |     def validate(self, value): | ||||||
|         assert isinstance(value, datetime.datetime) |         assert isinstance(value, datetime.datetime) | ||||||
|  |  | ||||||
|         super(DateTimeField, self).validate(value) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class EmbeddedDocumentField(BaseField): | class EmbeddedDocumentField(BaseField): | ||||||
|     """An embedded document field. Only valid values are subclasses of |     """An embedded document field. Only valid values are subclasses of | ||||||
|     :class:`~mongoengine.EmbeddedDocument`. |     :class:`~mongoengine.EmbeddedDocument`. | ||||||
| @@ -257,8 +239,6 @@ class EmbeddedDocumentField(BaseField): | |||||||
|                                   'provided to an EmbeddedDocumentField') |                                   'provided to an EmbeddedDocumentField') | ||||||
|         self.document.validate(value) |         self.document.validate(value) | ||||||
|  |  | ||||||
|         super(EmbeddedDocumentField, self).validate(value) |  | ||||||
|  |  | ||||||
|     def lookup_member(self, member_name): |     def lookup_member(self, member_name): | ||||||
|         return self.document._fields.get(member_name) |         return self.document._fields.get(member_name) | ||||||
|  |  | ||||||
| @@ -335,8 +315,6 @@ class ListField(BaseField): | |||||||
|         except Exception, err: |         except Exception, err: | ||||||
|             raise ValidationError('Invalid ListField item (%s)' % str(err)) |             raise ValidationError('Invalid ListField item (%s)' % str(err)) | ||||||
|  |  | ||||||
|         super(ListField, self).validate(value) |  | ||||||
|  |  | ||||||
|     def prepare_query_value(self, op, value): |     def prepare_query_value(self, op, value): | ||||||
|         if op in ('set', 'unset'): |         if op in ('set', 'unset'): | ||||||
|             return [self.field.to_mongo(v) for v in value] |             return [self.field.to_mongo(v) for v in value] | ||||||
| @@ -381,8 +359,6 @@ class DictField(BaseField): | |||||||
|             raise ValidationError('Invalid dictionary key name - keys may not ' |             raise ValidationError('Invalid dictionary key name - keys may not ' | ||||||
|                                   'contain "." or "$" characters') |                                   'contain "." or "$" characters') | ||||||
|  |  | ||||||
|         super(DictField, self).validate(value) |  | ||||||
|  |  | ||||||
|     def lookup_member(self, member_name): |     def lookup_member(self, member_name): | ||||||
|         return BaseField(db_field=member_name) |         return BaseField(db_field=member_name) | ||||||
|  |  | ||||||
| @@ -398,8 +374,6 @@ class GeoLocationField(DictField): | |||||||
|         if len(value) <> 2: |         if len(value) <> 2: | ||||||
|             raise ValidationError('GeoLocationField must have exactly two elements (x, y)') |             raise ValidationError('GeoLocationField must have exactly two elements (x, y)') | ||||||
|      |      | ||||||
|         super(GeoLocationField, self).validate(value) |  | ||||||
|      |  | ||||||
|     def to_mongo(self, value): |     def to_mongo(self, value): | ||||||
|         return {'x': value[0], 'y': value[1]} |         return {'x': value[0], 'y': value[1]} | ||||||
|      |      | ||||||
| @@ -469,8 +443,6 @@ class ReferenceField(BaseField): | |||||||
|     def validate(self, value): |     def validate(self, value): | ||||||
|         assert isinstance(value, (self.document_type, pymongo.dbref.DBRef)) |         assert isinstance(value, (self.document_type, pymongo.dbref.DBRef)) | ||||||
|  |  | ||||||
|         super(ReferenceField, self).validate(value) |  | ||||||
|  |  | ||||||
|     def lookup_member(self, member_name): |     def lookup_member(self, member_name): | ||||||
|         return self.document_type._fields.get(member_name) |         return self.document_type._fields.get(member_name) | ||||||
|  |  | ||||||
| @@ -540,5 +512,3 @@ class BinaryField(BaseField): | |||||||
|  |  | ||||||
|         if self.max_bytes is not None and len(value) > self.max_bytes: |         if self.max_bytes is not None and len(value) > self.max_bytes: | ||||||
|             raise ValidationError('Binary value is too long') |             raise ValidationError('Binary value is too long') | ||||||
|          |  | ||||||
|         super(BinaryField, self).validate(value) |  | ||||||
|   | |||||||
| @@ -588,5 +588,26 @@ class FieldTest(unittest.TestCase): | |||||||
|         AttachmentRequired.drop_collection() |         AttachmentRequired.drop_collection() | ||||||
|         AttachmentSizeLimit.drop_collection() |         AttachmentSizeLimit.drop_collection() | ||||||
|  |  | ||||||
|  |     def test_choices_validation(self): | ||||||
|  |         """Ensure that value is in a container of allowed values. | ||||||
|  |         """ | ||||||
|  |         class Shirt(Document): | ||||||
|  |             size = StringField(max_length=3, choices=('S','M','L','XL','XXL')) | ||||||
|  |  | ||||||
|  |         Shirt.drop_collection() | ||||||
|  |  | ||||||
|  |         shirt = Shirt() | ||||||
|  |         shirt.validate() | ||||||
|  |  | ||||||
|  |         shirt.size = "S" | ||||||
|  |         shirt.validate() | ||||||
|  |  | ||||||
|  |         shirt.size = "XS" | ||||||
|  |         self.assertRaises(ValidationError, shirt.validate) | ||||||
|  |  | ||||||
|  |         Shirt.drop_collection() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
|     unittest.main() |     unittest.main() | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user