Merge branch 'master' into deferred_fields
This commit is contained in:
commit
b0b8e11c60
3
.gitignore
vendored
3
.gitignore
vendored
@ -2,3 +2,6 @@
|
|||||||
.*.swp
|
.*.swp
|
||||||
docs/.build
|
docs/.build
|
||||||
docs/_build
|
docs/_build
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
mongoengine.egg-info/
|
@ -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
|
||||||
|
@ -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).
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user