Merge branch 'master' into deferred_fields

This commit is contained in:
blackbrrr 2010-01-14 11:39:09 -06:00
commit b0b8e11c60
8 changed files with 177 additions and 40 deletions

3
.gitignore vendored
View File

@ -2,3 +2,6 @@
.*.swp
docs/.build
docs/_build
build/
dist/
mongoengine.egg-info/

View File

@ -28,6 +28,8 @@ The :mod:`~mongoengine.django.auth` module also contains a
:func:`~mongoengine.django.auth.get_user` helper function, that takes a user's
:attr:`id` and returns a :class:`~mongoengine.django.auth.User` object.
.. versionadded:: 0.1.3
Sessions
========
Django allows the use of different backend stores for its sessions. MongoEngine
@ -40,3 +42,5 @@ session backend, ensure that your settings module has
into you settings module::
SESSION_ENGINE = 'mongoengine.django.sessions'
.. versionadded:: 0.2.1

View File

@ -69,7 +69,8 @@ class ObjectIdField(BaseField):
"""
def to_python(self, value):
return unicode(value)
return value
# return unicode(value)
def to_mongo(self, value):
if not isinstance(value, pymongo.objectid.ObjectId):
@ -246,6 +247,25 @@ class BaseDocument(object):
value = getattr(self, attr_name, None)
setattr(self, attr_name, value)
def validate(self):
"""Ensure that all fields' values are valid and that required fields
are present.
"""
# Get a list of tuples of field names and their current values
fields = [(field, getattr(self, name))
for name, field in self._fields.items()]
# Ensure that each field is matched to a valid value
for field, value in fields:
if value is not None:
try:
field.validate(value)
except (ValueError, AttributeError, AssertionError), e:
raise ValidationError('Invalid value for field of type "' +
field.__class__.__name__ + '"')
elif field.required:
raise ValidationError('Field "%s" is required' % field.name)
@classmethod
def _get_subclasses(cls):
"""Return a dictionary of all subclasses (found recursively).

View File

@ -107,25 +107,6 @@ class Document(BaseDocument):
for field in self._fields:
setattr(self, field, obj[field])
def validate(self):
"""Ensure that all fields' values are valid and that required fields
are present.
"""
# Get a list of tuples of field names and their current values
fields = [(field, getattr(self, name))
for name, field in self._fields.items()]
# Ensure that each field is matched to a valid value
for field, value in fields:
if value is not None:
try:
field.validate(value)
except (ValueError, AttributeError, AssertionError), e:
raise ValidationError('Invalid value for field of type "' +
field.__class__.__name__ + '"')
elif field.required:
raise ValidationError('Field "%s" is required' % field.name)
@classmethod
def drop_collection(cls):
"""Drops the entire collection associated with this

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

@ -150,9 +150,10 @@ class QuerySet(object):
def __call__(self, *q_objs, **query):
"""Filter the selected documents by calling the
:class:`~mongoengine.QuerySet` with a query.
:class:`~mongoengine.queryset.QuerySet` with a query.
:param q_objs: :class:`~mongoengine.Q` objects to be used in the query
:param q_objs: :class:`~mongoengine.queryset.Q` objects to be used in
the query
:param query: Django-style query keyword arguments
"""
for q in q_objs:
@ -162,7 +163,9 @@ class QuerySet(object):
return self
def filter(self, *q_objs, **query):
return self.__call__(**query)
"""An alias of :meth:`~mongoengine.queryset.QuerySet.__call__`
"""
return self.__call__(*q_objs, **query)
@property
def _collection(self):
@ -333,7 +336,16 @@ class QuerySet(object):
"""
# Slice provided
if isinstance(key, slice):
self._cursor_obj = self._cursor[key]
try:
self._cursor_obj = self._cursor[key]
except IndexError, err:
# PyMongo raises an error if key.start == key.stop, catch it,
# bin it, kill it.
if key.start >=0 and key.stop >= 0 and key.step is None:
if key.start == key.stop:
self.limit(0)
return self
raise err
# Allow further QuerySet modifications to be performed
return self
# Integer index provided

View File

@ -387,6 +387,25 @@ class DocumentTest(unittest.TestCase):
self.assertTrue('content' in Comment._fields)
self.assertFalse('id' in Comment._fields)
self.assertFalse(hasattr(Comment, '_meta'))
def test_embedded_document_validation(self):
"""Ensure that embedded documents may be validated.
"""
class Comment(EmbeddedDocument):
date = DateTimeField()
content = StringField(required=True)
comment = Comment()
self.assertRaises(ValidationError, comment.validate)
comment.content = 'test'
comment.validate()
comment.date = 4
self.assertRaises(ValidationError, comment.validate)
comment.date = datetime.datetime.now()
comment.validate()
def test_save(self):
"""Ensure that a document may be saved in the database.
@ -399,7 +418,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.