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 document import Document, EmbeddedDocument
from connection import _get_db
import re
import pymongo
import datetime
import decimal
__all__ = ['StringField', 'IntField', 'FloatField', 'BooleanField',
'DateTimeField', 'EmbeddedDocumentField', 'ListField',
'ObjectIdField', 'ReferenceField', 'ValidationError']
'DateTimeField', 'EmbeddedDocumentField', 'ListField',
'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):
"""A unicode string field.
"""
def __init__(self, regex=None, max_length=None, **kwargs):
self.regex = re.compile(regex) if regex else None
self.max_length = max_length
super(StringField, self).__init__(**kwargs)
def to_python(self, value):
return unicode(value)
@ -38,6 +49,25 @@ class StringField(BaseField):
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):
"""An integer field.
"""
@ -45,12 +75,15 @@ class IntField(BaseField):
def __init__(self, min_value=None, max_value=None, **kwargs):
self.min_value, self.max_value = min_value, max_value
super(IntField, self).__init__(**kwargs)
def to_python(self, value):
return int(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:
raise ValidationError('Integer value is too small')
@ -66,7 +99,7 @@ class FloatField(BaseField):
def __init__(self, min_value=None, max_value=None, **kwargs):
self.min_value, self.max_value = min_value, max_value
super(FloatField, self).__init__(**kwargs)
def to_python(self, value):
return float(value)
@ -80,12 +113,41 @@ class FloatField(BaseField):
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):
"""A boolean field type.
.. versionadded:: 0.1.2
"""
def to_python(self, value):
return bool(value)
@ -102,8 +164,8 @@ class DateTimeField(BaseField):
class EmbeddedDocumentField(BaseField):
"""An embedded document field. Only valid values are subclasses of
:class:`~mongoengine.EmbeddedDocument`.
"""An embedded document field. Only valid values are subclasses of
:class:`~mongoengine.EmbeddedDocument`.
"""
def __init__(self, document, **kwargs):
@ -112,7 +174,7 @@ class EmbeddedDocumentField(BaseField):
'to an EmbeddedDocumentField')
self.document = document
super(EmbeddedDocumentField, self).__init__(**kwargs)
def to_python(self, value):
if not isinstance(value, self.document):
return self.document._from_son(value)
@ -122,7 +184,7 @@ class EmbeddedDocumentField(BaseField):
return self.document.to_mongo(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.
"""
# Using isinstance also works for subclasses of self.document
@ -202,7 +264,7 @@ class ReferenceField(BaseField):
value = _get_db().dereference(value)
if value is not None:
instance._data[self.name] = self.document_type._from_son(value)
return super(ReferenceField, self).__get__(instance, owner)
def to_mongo(self, document):
@ -222,7 +284,7 @@ class ReferenceField(BaseField):
collection = self.document_type._meta['collection']
return pymongo.dbref.DBRef(collection, id_)
def prepare_query_value(self, value):
return self.to_mongo(value)

View File

@ -399,7 +399,7 @@ class DocumentTest(unittest.TestCase):
person_obj = collection.find_one({'name': 'Test User'})
self.assertEqual(person_obj['name'], 'Test User')
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):
"""Ensure that document may be deleted using the delete method.

View File

@ -1,5 +1,6 @@
import unittest
import datetime
from decimal import Decimal
from mongoengine import *
from mongoengine.connection import _get_db
@ -78,6 +79,23 @@ class FieldTest(unittest.TestCase):
person.name = 'Shorter name'
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):
"""Ensure that invalid values cannot be assigned to int fields.
@ -112,6 +130,24 @@ class FieldTest(unittest.TestCase):
self.assertRaises(ValidationError, person.validate)
person.height = 4.0
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):
"""Ensure that invalid values cannot be assigned to boolean fields.