added URLField, DecimalField, tests.

This commit is contained in:
blackbrrr 2010-01-14 11:32:28 -06:00
parent 1cdeb8130d
commit 5f84d6f8f8
3 changed files with 114 additions and 16 deletions

View File

@ -1,26 +1,37 @@
from base import BaseField, ObjectIdField, ValidationError from base import BaseField, ObjectIdField, ValidationError
from document import Document, EmbeddedDocument from document import Document, EmbeddedDocument
from connection import _get_db from connection import _get_db
import re import re
import pymongo import pymongo
import datetime import datetime
import decimal
__all__ = ['StringField', 'IntField', 'FloatField', 'BooleanField', __all__ = ['StringField', 'IntField', 'FloatField', 'BooleanField',
'DateTimeField', 'EmbeddedDocumentField', 'ListField', 'DateTimeField', 'EmbeddedDocumentField', 'ListField',
'ObjectIdField', 'ReferenceField', 'ValidationError'] 'ObjectIdField', 'ReferenceField', 'ValidationError',
'URLField', 'DecimalField']
URL_REGEX = re.compile(
r'^https?://'
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|'
r'localhost|'
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'
r'(?::\d+)?'
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
class StringField(BaseField): class StringField(BaseField):
"""A unicode string field. """A unicode string field.
""" """
def __init__(self, regex=None, max_length=None, **kwargs): def __init__(self, regex=None, max_length=None, **kwargs):
self.regex = re.compile(regex) if regex else None self.regex = re.compile(regex) if regex else None
self.max_length = max_length self.max_length = max_length
super(StringField, self).__init__(**kwargs) super(StringField, self).__init__(**kwargs)
def to_python(self, value): def to_python(self, value):
return unicode(value) return unicode(value)
@ -38,6 +49,25 @@ class StringField(BaseField):
return None return None
class URLField(BaseField):
"""A field that validates input as a URL.
"""
def __init__(self, verify_exists=True, **kwargs):
self.verify_exists = verify_exists
super(URLField, self).__init__(**kwargs)
def validate(self, value):
import urllib2
if self.verify_exists:
try:
request = urllib2.Request(value)
response = urllib2.urlopen(request)
except Exception, e:
raise ValidationError('This URL appears to be invalid: %s' % e)
class IntField(BaseField): class IntField(BaseField):
"""An integer field. """An integer field.
""" """
@ -45,12 +75,15 @@ class IntField(BaseField):
def __init__(self, min_value=None, max_value=None, **kwargs): def __init__(self, min_value=None, max_value=None, **kwargs):
self.min_value, self.max_value = min_value, max_value self.min_value, self.max_value = min_value, max_value
super(IntField, self).__init__(**kwargs) super(IntField, self).__init__(**kwargs)
def to_python(self, value): def to_python(self, value):
return int(value) return int(value)
def validate(self, value): def validate(self, value):
assert isinstance(value, (int, long)) try:
value = int(value)
except:
raise ValidationError('%s could not be converted to int' % value)
if self.min_value is not None and value < self.min_value: if self.min_value is not None and value < self.min_value:
raise ValidationError('Integer value is too small') raise ValidationError('Integer value is too small')
@ -66,7 +99,7 @@ class FloatField(BaseField):
def __init__(self, min_value=None, max_value=None, **kwargs): def __init__(self, min_value=None, max_value=None, **kwargs):
self.min_value, self.max_value = min_value, max_value self.min_value, self.max_value = min_value, max_value
super(FloatField, self).__init__(**kwargs) super(FloatField, self).__init__(**kwargs)
def to_python(self, value): def to_python(self, value):
return float(value) return float(value)
@ -80,12 +113,41 @@ class FloatField(BaseField):
raise ValidationError('Float value is too large') raise ValidationError('Float value is too large')
class DecimalField(BaseField):
"""A fixed-point decimal number field.
"""
def __init__(self, min_value=None, max_value=None, **kwargs):
self.min_value, self.max_value = min_value, max_value
super(DecimalField, self).__init__(**kwargs)
def to_python(self, value):
if not isinstance(value, basestring):
value = unicode(value)
return decimal.Decimal(value)
def validate(self, value):
if not isinstance(value, decimal.Decimal):
if not isinstance(value, basestring):
value = str(value)
try:
value = decimal.Decimal(value)
except Exception, exc:
raise ValidationError('Could not convert to decimal: %s' % exc)
if self.min_value is not None and value < self.min_value:
raise ValidationError('Decimal value is too small')
if self.max_value is not None and vale > self.max_value:
raise ValidationError('Decimal value is too large')
class BooleanField(BaseField): class BooleanField(BaseField):
"""A boolean field type. """A boolean field type.
.. versionadded:: 0.1.2 .. versionadded:: 0.1.2
""" """
def to_python(self, value): def to_python(self, value):
return bool(value) return bool(value)
@ -102,8 +164,8 @@ class DateTimeField(BaseField):
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`.
""" """
def __init__(self, document, **kwargs): def __init__(self, document, **kwargs):
@ -112,7 +174,7 @@ class EmbeddedDocumentField(BaseField):
'to an EmbeddedDocumentField') 'to an EmbeddedDocumentField')
self.document = document self.document = document
super(EmbeddedDocumentField, self).__init__(**kwargs) super(EmbeddedDocumentField, self).__init__(**kwargs)
def to_python(self, value): def to_python(self, value):
if not isinstance(value, self.document): if not isinstance(value, self.document):
return self.document._from_son(value) return self.document._from_son(value)
@ -122,7 +184,7 @@ class EmbeddedDocumentField(BaseField):
return self.document.to_mongo(value) return self.document.to_mongo(value)
def validate(self, value): def validate(self, value):
"""Make sure that the document instance is an instance of the """Make sure that the document instance is an instance of the
EmbeddedDocument subclass provided when the document was defined. EmbeddedDocument subclass provided when the document was defined.
""" """
# Using isinstance also works for subclasses of self.document # Using isinstance also works for subclasses of self.document
@ -202,7 +264,7 @@ class ReferenceField(BaseField):
value = _get_db().dereference(value) value = _get_db().dereference(value)
if value is not None: if value is not None:
instance._data[self.name] = self.document_type._from_son(value) instance._data[self.name] = self.document_type._from_son(value)
return super(ReferenceField, self).__get__(instance, owner) return super(ReferenceField, self).__get__(instance, owner)
def to_mongo(self, document): def to_mongo(self, document):
@ -222,7 +284,7 @@ class ReferenceField(BaseField):
collection = self.document_type._meta['collection'] collection = self.document_type._meta['collection']
return pymongo.dbref.DBRef(collection, id_) return pymongo.dbref.DBRef(collection, id_)
def prepare_query_value(self, value): def prepare_query_value(self, value):
return self.to_mongo(value) return self.to_mongo(value)

View File

@ -399,7 +399,7 @@ class DocumentTest(unittest.TestCase):
person_obj = collection.find_one({'name': 'Test User'}) person_obj = collection.find_one({'name': 'Test User'})
self.assertEqual(person_obj['name'], 'Test User') self.assertEqual(person_obj['name'], 'Test User')
self.assertEqual(person_obj['age'], 30) self.assertEqual(person_obj['age'], 30)
self.assertEqual(str(person_obj['_id']), person.id) self.assertEqual(person_obj['_id'], person.id)
def test_delete(self): def test_delete(self):
"""Ensure that document may be deleted using the delete method. """Ensure that document may be deleted using the delete method.

View File

@ -1,5 +1,6 @@
import unittest import unittest
import datetime import datetime
from decimal import Decimal
from mongoengine import * from mongoengine import *
from mongoengine.connection import _get_db from mongoengine.connection import _get_db
@ -78,6 +79,23 @@ class FieldTest(unittest.TestCase):
person.name = 'Shorter name' person.name = 'Shorter name'
person.validate() person.validate()
def test_url_validation(self):
"""Ensure that invalid URLs cannot be assigned to URL fields.
"""
class Person(Document):
name = StringField()
personal_blog = URLField()
person = Person()
person.name = "Guido van Rossum"
person.personal_blog = "pep8 or bust!"
self.assertRaises(ValidationError, person.validate)
# swap in a real URL
person.personal_blog = "http://neopythonic.blogspot.com/"
person.validate()
def test_int_validation(self): def test_int_validation(self):
"""Ensure that invalid values cannot be assigned to int fields. """Ensure that invalid values cannot be assigned to int fields.
@ -112,6 +130,24 @@ class FieldTest(unittest.TestCase):
self.assertRaises(ValidationError, person.validate) self.assertRaises(ValidationError, person.validate)
person.height = 4.0 person.height = 4.0
self.assertRaises(ValidationError, person.validate) self.assertRaises(ValidationError, person.validate)
def test_decimal_validation(self):
"""Ensure that invalid values cannot be assigned to decimal fields.
"""
class AlbumReview(Document):
score = DecimalField()
review = AlbumReview()
review.score = "8.7"
review.validate()
review.score = Decimal("10.0")
review.validate()
# implicit conversion from float to string
review.score = 3.14
review.validate()
review.score = "it stinks!"
self.assertRaises(ValidationError, review.validate)
def test_boolean_validation(self): def test_boolean_validation(self):
"""Ensure that invalid values cannot be assigned to boolean fields. """Ensure that invalid values cannot be assigned to boolean fields.