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 .*.swp
docs/.build docs/.build
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 :func:`~mongoengine.django.auth.get_user` helper function, that takes a user's
:attr:`id` and returns a :class:`~mongoengine.django.auth.User` object. :attr:`id` and returns a :class:`~mongoengine.django.auth.User` object.
.. versionadded:: 0.1.3
Sessions Sessions
======== ========
Django allows the use of different backend stores for its sessions. MongoEngine 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:: into you settings module::
SESSION_ENGINE = 'mongoengine.django.sessions' SESSION_ENGINE = 'mongoengine.django.sessions'
.. versionadded:: 0.2.1

View File

@ -69,7 +69,8 @@ class ObjectIdField(BaseField):
""" """
def to_python(self, value): def to_python(self, value):
return unicode(value) return value
# return unicode(value)
def to_mongo(self, value): def to_mongo(self, value):
if not isinstance(value, pymongo.objectid.ObjectId): if not isinstance(value, pymongo.objectid.ObjectId):
@ -246,6 +247,25 @@ class BaseDocument(object):
value = getattr(self, attr_name, None) value = getattr(self, attr_name, None)
setattr(self, attr_name, value) 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 @classmethod
def _get_subclasses(cls): def _get_subclasses(cls):
"""Return a dictionary of all subclasses (found recursively). """Return a dictionary of all subclasses (found recursively).

View File

@ -107,25 +107,6 @@ class Document(BaseDocument):
for field in self._fields: for field in self._fields:
setattr(self, field, obj[field]) 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 @classmethod
def drop_collection(cls): def drop_collection(cls):
"""Drops the entire collection associated with this """Drops the entire collection associated with this

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

@ -150,9 +150,10 @@ class QuerySet(object):
def __call__(self, *q_objs, **query): def __call__(self, *q_objs, **query):
"""Filter the selected documents by calling the """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 :param query: Django-style query keyword arguments
""" """
for q in q_objs: for q in q_objs:
@ -162,7 +163,9 @@ class QuerySet(object):
return self return self
def filter(self, *q_objs, **query): 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 @property
def _collection(self): def _collection(self):
@ -333,7 +336,16 @@ class QuerySet(object):
""" """
# Slice provided # Slice provided
if isinstance(key, slice): 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 # Allow further QuerySet modifications to be performed
return self return self
# Integer index provided # Integer index provided

View File

@ -387,6 +387,25 @@ class DocumentTest(unittest.TestCase):
self.assertTrue('content' in Comment._fields) self.assertTrue('content' in Comment._fields)
self.assertFalse('id' in Comment._fields) self.assertFalse('id' in Comment._fields)
self.assertFalse(hasattr(Comment, '_meta')) 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): def test_save(self):
"""Ensure that a document may be saved in the database. """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'}) 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.