Compare commits

...

21 Commits
v0.7.5 ... 0.7

Author SHA1 Message Date
Ross Lawley
31ec7907b5 Fixing py3 compat 2012-10-01 20:01:43 +00:00
Ross Lawley
12f3f8c694 Added chaining regression test (MongoEngine/mongoengine#135) 2012-10-01 16:27:59 +00:00
Ross Lawley
79098e997e Updated test 2012-10-01 16:20:48 +00:00
Ross Lawley
dc1849bad5 Unicode fix for repr (MongoEngine/mongoengine#133) 2012-10-01 16:15:25 +00:00
Ross Lawley
e2d826c412 Allow updates with match operators (MongoEngine/mongoengine#144) 2012-10-01 15:26:34 +00:00
Ross Lawley
e6d796832e Unicode fix reverted but can have custom validator
MongoEngine/mongoengine#136
2012-10-01 14:48:53 +00:00
Ross Lawley
6f0a6df4f6 Fix loop 2012-10-01 14:23:05 +00:00
Ross Lawley
7a877a00d5 Updated URLField
handle unicode and custom validator (MongoEngine/mongoengine#136)
2012-10-01 13:59:15 +00:00
Ross Lawley
e8604d100e Added Garry Polley to contributors list
hmarr/mongoengine#573
2012-10-01 10:20:28 +00:00
Ross Lawley
1647441ce8 Merge branch 'master' of github.com:hmarr/mongoengine 2012-10-01 10:17:01 +00:00
Ross Lawley
9f8d6b3a00 Merge pull request #573 from garrypolley/master
Allow Django  AuthenticationBackends to work with Django user

Thanks @garrypolley
2012-10-01 00:24:52 -07:00
Garry Polley
4b2ad25405 can now use AuthenticationBackends with permissions. 2012-09-27 10:21:05 -05:00
Ross Lawley
3ce163b1a0 Updates to the readme 2012-09-27 10:29:24 +00:00
Ross Lawley
7c1ee28f13 Added CONTRIBUTING.rst 2012-09-27 10:26:22 +00:00
Ross Lawley
2645e43da1 Merge branch 'master' into 0.7 2012-09-27 08:30:36 +00:00
Ross Lawley
59bfe551a3 Updated docs thanks @mitar 2012-09-27 08:29:49 +00:00
Ross Lawley
e2c78047b1 Moved injection of Exceptions to top level 2012-09-26 10:43:14 +00:00
Ross Lawley
6a4351e44f Fixed reload issue with ReferenceField where dbref=False (MongoEngine/mongoengine#138) 2012-09-24 18:49:29 +00:00
Ross Lawley
adb60ef1ac Improved import cache 2012-09-24 18:45:02 +00:00
Ross Lawley
3090adac04 Fixed objectId for DBRef 2012-09-24 11:37:54 +00:00
Ross Lawley
b9253d86cc Updated travis.yml 2012-09-19 18:53:31 +00:00
16 changed files with 263 additions and 58 deletions

View File

@@ -1,5 +1,6 @@
# http://travis-ci.org/#!/MongoEngine/mongoengine
language: python
services: mongodb
python:
- 2.5
- 2.6

View File

@@ -123,3 +123,4 @@ that much better:
* psychogenic
* Stefan Wójcik
* dimonb
* Garry Polley

61
CONTRIBUTING.rst Normal file
View File

@@ -0,0 +1,61 @@
Contributing to MongoEngine
===========================
MongoEngine has a large `community
<https://raw.github.com/MongoEngine/mongoengine/master/AUTHORS>`_ and
contributions are always encouraged. Contributions can be as simple as
minor tweaks to the documentation. Please read these guidelines before
sending a pull request.
Bugfixes and New Features
-------------------------
Before starting to write code, look for existing `tickets
<https://github.com/MongoEngine/mongoengine/issues?state=open>`_ or `create one
<https://github.com/MongoEngine/mongoengine/issues>`_ for your specific
issue or feature request. That way you avoid working on something
that might not be of interest or that has already been addressed. If in doubt
post to the `user group <http://groups.google.com/group/mongoengine-users>`
Supported Interpreters
----------------------
PyMongo supports CPython 2.5 and newer. Language
features not supported by all interpreters can not be used.
Please also ensure that your code is properly converted by
`2to3 <http://docs.python.org/library/2to3.html>`_ for Python 3 support.
Style Guide
-----------
MongoEngine aims to follow `PEP8 <http://www.python.org/dev/peps/pep-0008/>`_
including 4 space indents and 79 character line limits.
Testing
-------
All tests are run on `Travis <http://travis-ci.org/MongoEngine/mongoengine>`_
and any pull requests are automatically tested by Travis. Any pull requests
without tests will take longer to be integrated and might be refused.
General Guidelines
------------------
- Avoid backward breaking changes if at all possible.
- Write inline documentation for new classes and methods.
- Write tests and make sure they pass (make sure you have a mongod
running on the default port, then execute ``python setup.py test``
from the cmd line to run the test suite).
- Add yourself to AUTHORS.rst :)
Documentation
-------------
To contribute to the `API documentation
<http://docs.mongoengine.org/en/latest/apireference.html>`_
just make your changes to the inline documentation of the appropriate
`source code <https://github.com/MongoEngine/mongoengine>`_ or `rst file
<https://github.com/MongoEngine/mongoengine/tree/master/docs>`_ in a
branch and submit a `pull request <https://help.github.com/articles/using-pull-requests>`_.
You might also use the github `Edit <https://github.com/blog/844-forking-with-the-edit-button>`_
button.

View File

@@ -92,6 +92,4 @@ Community
Contributing
============
The source is available on `GitHub <http://github.com/MongoEngine/mongoengine>`_ - to
contribute to the project, fork it on GitHub and send a pull request, all
contributions and suggestions are welcome!
We welcome contributions! see the`Contribution guidelines <https://github.com/MongoEngine/mongoengine/blob/master/CONTRIBUTING.rst>`_

View File

@@ -2,6 +2,14 @@
Changelog
=========
Changes in 0.7.X
================
- Unicode fix for repr (MongoEngine/mongoengine#133)
- Allow updates with match operators (MongoEngine/mongoengine#144)
- Updated URLField - now can have a override the regex (MongoEngine/mongoengine#136)
- Allow Django AuthenticationBackends to work with Django user (hmarr/mongoengine#573)
- Fixed reload issue with ReferenceField where dbref=False (MongoEngine/mongoengine#138)
Changes in 0.7.5
================
- ReferenceFields with dbref=False use ObjectId instead of strings (MongoEngine/mongoengine#134)

View File

@@ -469,7 +469,7 @@ If a dictionary is passed then the following options are available:
Whether the index should be sparse.
:attr:`unique` (Default: False)
Whether the index should be sparse.
Whether the index should be unique.
.. note ::

View File

@@ -53,7 +53,7 @@ class ValidationError(AssertionError):
self.message = message
def __str__(self):
return self.message
return txt_type(self.message)
def __repr__(self):
return '%s(%s,)' % (self.__class__.__name__, self.message)
@@ -235,7 +235,8 @@ class BaseField(object):
pass
def _validate(self, value):
from mongoengine import Document, EmbeddedDocument
Document = _import_class('Document')
EmbeddedDocument = _import_class('EmbeddedDocument')
# check choices
if self.choices:
is_cls = isinstance(value, (Document, EmbeddedDocument))
@@ -283,7 +284,9 @@ class ComplexBaseField(BaseField):
if instance is None:
# Document class being used rather than a document object
return self
from fields import GenericReferenceField, ReferenceField
ReferenceField = _import_class('ReferenceField')
GenericReferenceField = _import_class('GenericReferenceField')
dereference = self.field is None or isinstance(self.field,
(GenericReferenceField, ReferenceField))
if not self._dereference and instance._initialised and dereference:
@@ -310,6 +313,7 @@ class ComplexBaseField(BaseField):
)
value._dereferenced = True
instance._data[self.name] = value
return value
def __set__(self, instance, value):
@@ -321,7 +325,7 @@ class ComplexBaseField(BaseField):
def to_python(self, value):
"""Convert a MongoDB-compatible type to a Python type.
"""
from mongoengine import Document
Document = _import_class('Document')
if isinstance(value, basestring):
return value
@@ -363,7 +367,7 @@ class ComplexBaseField(BaseField):
def to_mongo(self, value):
"""Convert a Python type to a MongoDB-compatible type.
"""
from mongoengine import Document
Document = _import_class("Document")
if isinstance(value, basestring):
return value
@@ -399,7 +403,7 @@ class ComplexBaseField(BaseField):
meta.get('allow_inheritance', ALLOW_INHERITANCE)
== False)
if allow_inheritance and not self.field:
from fields import GenericReferenceField
GenericReferenceField = _import_class("GenericReferenceField")
value_dict[k] = GenericReferenceField().to_mongo(v)
else:
collection = v._get_collection_name()
@@ -460,7 +464,7 @@ class ComplexBaseField(BaseField):
@property
def _dereference(self,):
if not self.__dereference:
from dereference import DeReference
DeReference = _import_class("DeReference")
self.__dereference = DeReference() # Cached
return self.__dereference
@@ -631,17 +635,6 @@ class DocumentMetaclass(type):
"field name" % field.name)
raise InvalidDocumentError(msg)
# Merge in exceptions with parent hierarchy
exceptions_to_merge = (DoesNotExist, MultipleObjectsReturned)
module = attrs.get('__module__')
for exc in exceptions_to_merge:
name = exc.__name__
parents = tuple(getattr(base, name) for base in flattened_bases
if hasattr(base, name)) or (exc,)
# Create new exception and set to new_class
exception = type(name, parents, {'__module__': module})
setattr(new_class, name, exception)
# Add class to the _document_registry
_document_registry[new_class._class_name] = new_class
@@ -832,6 +825,17 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
new_class._fields['id'] = ObjectIdField(db_field='_id')
new_class.id = new_class._fields['id']
# Merge in exceptions with parent hierarchy
exceptions_to_merge = (DoesNotExist, MultipleObjectsReturned)
module = attrs.get('__module__')
for exc in exceptions_to_merge:
name = exc.__name__
parents = tuple(getattr(base, name) for base in flattened_bases
if hasattr(base, name)) or (exc,)
# Create new exception and set to new_class
exception = type(name, parents, {'__module__': module})
setattr(new_class, name, exception)
return new_class
@classmethod
@@ -943,7 +947,7 @@ class BaseDocument(object):
field = None
if not hasattr(self, name) and not name.startswith('_'):
from fields import DynamicField
DynamicField = _import_class("DynamicField")
field = DynamicField(db_field=name)
field.name = name
self._dynamic_fields[name] = field
@@ -1121,7 +1125,8 @@ class BaseDocument(object):
def _get_changed_fields(self, key='', inspected=None):
"""Returns a list of all fields that have explicitly been changed.
"""
from mongoengine import EmbeddedDocument, DynamicEmbeddedDocument
EmbeddedDocument = _import_class("EmbeddedDocument")
DynamicEmbeddedDocument = _import_class("DynamicEmbeddedDocument")
_changed_fields = []
_changed_fields += getattr(self, '_changed_fields', [])
@@ -1252,7 +1257,9 @@ class BaseDocument(object):
geo_indices = []
inspected.append(cls)
from fields import EmbeddedDocumentField, GeoPointField
EmbeddedDocumentField = _import_class("EmbeddedDocumentField")
GeoPointField = _import_class("GeoPointField")
for field in cls._fields.values():
if not isinstance(field, (EmbeddedDocumentField, GeoPointField)):
continue
@@ -1326,10 +1333,11 @@ class BaseDocument(object):
def __repr__(self):
try:
u = txt_type(self)
u = self.__str__()
except (UnicodeEncodeError, UnicodeDecodeError):
u = '[Bad Unicode data]'
return '<%s: %s>' % (self.__class__.__name__, u)
repr_type = type(u)
return repr_type('<%s: %s>' % (self.__class__.__name__, u))
def __str__(self):
if hasattr(self, '__unicode__'):
@@ -1337,7 +1345,7 @@ class BaseDocument(object):
return self.__unicode__()
else:
return unicode(self).encode('utf-8')
return '%s object' % self.__class__.__name__
return txt_type('%s object' % self.__class__.__name__)
def __eq__(self, other):
if isinstance(other, self.__class__) and hasattr(other, 'id'):
@@ -1486,14 +1494,30 @@ def _import_class(cls_name):
"""Cached mechanism for imports"""
if cls_name in _class_registry:
return _class_registry.get(cls_name)
if cls_name == 'Document':
from mongoengine.document import Document as cls
elif cls_name == 'EmbeddedDocument':
from mongoengine.document import EmbeddedDocument as cls
elif cls_name == 'DictField':
from mongoengine.fields import DictField as cls
elif cls_name == 'OperationError':
from queryset import OperationError as cls
_class_registry[cls_name] = cls
return cls
doc_classes = ['Document', 'DynamicEmbeddedDocument', 'EmbeddedDocument']
field_classes = ['DictField', 'DynamicField', 'EmbeddedDocumentField',
'GenericReferenceField', 'GeoPointField',
'ReferenceField']
queryset_classes = ['OperationError']
deref_classes = ['DeReference']
if cls_name in doc_classes:
from mongoengine import document as module
import_classes = doc_classes
elif cls_name in field_classes:
from mongoengine import fields as module
import_classes = field_classes
elif cls_name in queryset_classes:
from mongoengine import queryset as module
import_classes = queryset_classes
elif cls_name in deref_classes:
from mongoengine import dereference as module
import_classes = deref_classes
else:
raise ValueError('No import set for: ' % cls_name)
for cls in import_classes:
_class_registry[cls] = getattr(module, cls)
return _class_registry.get(cls_name)

View File

@@ -31,10 +31,10 @@ class DeReference(object):
items = [i for i in items]
self.max_depth = max_depth
doc_type = None
if instance and instance._fields:
doc_type = instance._fields[name]
doc_type = instance._fields.get(name)
if hasattr(doc_type, 'field'):
doc_type = doc_type.field
@@ -134,7 +134,7 @@ class DeReference(object):
elif doc_type is None:
doc = get_document(
''.join(x.capitalize()
for x in col.split('_')))._from_son(ref)
for x in col.split('_')))._from_son(ref)
else:
doc = doc_type._from_son(ref)
object_map[doc.id] = doc
@@ -166,7 +166,7 @@ class DeReference(object):
return self.object_map.get(items['_ref'].id, items)
elif '_types' in items and '_cls' in items:
doc = get_document(items['_cls'])._from_son(items)
doc._data = self._attach_objects(doc._data, depth, doc, name)
doc._data = self._attach_objects(doc._data, depth, doc, None)
return doc
if not hasattr(items, 'items'):

View File

@@ -3,6 +3,8 @@ import datetime
from mongoengine import *
from django.utils.encoding import smart_str
from django.contrib.auth.models import _user_get_all_permissions
from django.contrib.auth.models import _user_has_perm
from django.contrib.auth.models import AnonymousUser
from django.utils.translation import ugettext_lazy as _
@@ -104,6 +106,25 @@ class User(Document):
"""
return check_password(raw_password, self.password)
def get_all_permissions(self, obj=None):
return _user_get_all_permissions(self, obj)
def has_perm(self, perm, obj=None):
"""
Returns True if the user has the specified permission. This method
queries all available auth backends, but returns immediately if any
backend returns True. Thus, a user who has permission from a single
auth backend is assumed to have permission in general. If an object is
provided, permissions for this specific object are checked.
"""
# Active superusers have all permissions.
if self.is_active and self.is_superuser:
return True
# Otherwise we need to check the backends.
return _user_has_perm(self, perm, obj)
@classmethod
def create_user(cls, username, password, email=None):
"""Create (and save) a new user with the given username, password and

View File

@@ -361,7 +361,12 @@ class Document(BaseDocument):
id_field = self._meta['id_field']
obj = self.__class__.objects(
**{id_field: self[id_field]}
).first().select_related(max_depth=max_depth)
).limit(1).select_related(max_depth=max_depth)
if obj:
obj = obj[0]
else:
msg = "Reloaded document has been deleted"
raise OperationError(msg)
for field in self._fields:
setattr(self, field, self._reload(field, obj[field]))
if self._dynamic:

View File

@@ -1,10 +1,12 @@
import datetime
import decimal
import itertools
import re
import time
import urllib2
import urlparse
import uuid
import warnings
import itertools
from operator import itemgetter
import gridfs
@@ -101,25 +103,30 @@ class URLField(StringField):
.. versionadded:: 0.3
"""
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
)
_URL_REGEX = re.compile(
r'^(?:http|ftp)s?://' # http:// or https://
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' #domain...
r'localhost|' #localhost...
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
r'(?::\d+)?' # optional port
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
def __init__(self, verify_exists=False, **kwargs):
def __init__(self, verify_exists=False, url_regex=None, **kwargs):
self.verify_exists = verify_exists
self.url_regex = url_regex or self._URL_REGEX
super(URLField, self).__init__(**kwargs)
def validate(self, value):
if not URLField.URL_REGEX.match(value):
if not self.url_regex.match(value):
self.error('Invalid URL: %s' % value)
return
if self.verify_exists:
import urllib2
warnings.warn(
"The URLField verify_exists argument has intractable security "
"and performance issues. Accordingly, it has been deprecated.",
DeprecationWarning
)
try:
request = urllib2.Request(value)
urllib2.urlopen(request)
@@ -770,7 +777,7 @@ class ReferenceField(BaseField):
def to_mongo(self, document):
if isinstance(document, DBRef):
if not self.dbref:
return "%s" % DBRef.id
return DBRef.id
return document
elif not self.dbref and isinstance(document, basestring):
return document

View File

@@ -1395,6 +1395,8 @@ class QuerySet(object):
"""
operators = ['set', 'unset', 'inc', 'dec', 'pop', 'push', 'push_all',
'pull', 'pull_all', 'add_to_set']
match_operators = ['ne', 'gt', 'gte', 'lt', 'lte', 'in', 'nin', 'mod',
'all', 'size', 'exists', 'not']
mongo_update = {}
for key, value in update.items():
@@ -1418,6 +1420,10 @@ class QuerySet(object):
elif op == 'add_to_set':
op = op.replace('_to_set', 'ToSet')
match = None
if parts[-1] in match_operators:
match = parts.pop()
if _doc_cls:
# Switch field names to proper names [set in Field(name='foo')]
fields = QuerySet._lookup_field(_doc_cls, parts)
@@ -1451,16 +1457,22 @@ class QuerySet(object):
elif field.required or value is not None:
value = field.prepare_query_value(op, value)
if match:
match = '$' + match
value = {match: value}
key = '.'.join(parts)
if not op:
raise InvalidQueryError("Updates must supply an operation eg: set__FIELD=value")
raise InvalidQueryError("Updates must supply an operation "
"eg: set__FIELD=value")
if 'pull' in op and '.' in key:
# Dot operators don't work on pull operations
# it uses nested dict syntax
if op == 'pullAll':
raise InvalidQueryError("pullAll operations only support a single field depth")
raise InvalidQueryError("pullAll operations only support "
"a single field depth")
parts.reverse()
for key in parts:

View File

@@ -84,6 +84,7 @@ class FieldTest(unittest.TestCase):
group = Group(members=User.objects)
group.save()
group.reload() # Confirm reload works
with query_counter() as q:
self.assertEqual(q, 0)

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import with_statement
import bson
import os
@@ -85,6 +86,22 @@ class DocumentTest(unittest.TestCase):
# Ensure Document isn't treated like an actual document
self.assertFalse(hasattr(Document, '_fields'))
def test_repr(self):
"""Ensure that unicode representation works
"""
class Article(Document):
title = StringField()
def __unicode__(self):
return self.title
Article.drop_collection()
Article(title=u'привет мир').save()
self.assertEqual('<Article: привет мир>', repr(Article.objects.first()))
self.assertEqual('[<Article: привет мир>]', repr(Article.objects.all()))
def test_collection_naming(self):
"""Ensure that a collection with a specified name may be used.
"""

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import with_statement
import datetime
import os

View File

@@ -230,6 +230,30 @@ class QuerySetTest(unittest.TestCase):
Blog.drop_collection()
def test_chaining(self):
class A(Document):
pass
class B(Document):
a = ReferenceField(A)
A.drop_collection()
B.drop_collection()
a1 = A().save()
a2 = A().save()
B(a=a1).save()
# Works
q1 = B.objects.filter(a__in=[a1, a2], a=a1)._query
# Doesn't work
q2 = B.objects.filter(a__in=[a1, a2])
q2 = q2.filter(a=a1)._query
self.assertEqual(q1, q2)
def test_update_write_options(self):
"""Test that passing write_options works"""
@@ -414,6 +438,30 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(post.comments[0].by, 'joe')
self.assertEqual(post.comments[0].votes.score, 4)
def test_updates_can_have_match_operators(self):
class Post(Document):
title = StringField(required=True)
tags = ListField(StringField())
comments = ListField(EmbeddedDocumentField("Comment"))
class Comment(EmbeddedDocument):
content = StringField()
name = StringField(max_length=120)
vote = IntField()
Post.drop_collection()
comm1 = Comment(content="very funny indeed", name="John S", vote=1)
comm2 = Comment(content="kind of funny", name="Mark P", vote=0)
Post(title='Fun with MongoEngine', tags=['mongodb', 'mongoengine'],
comments=[comm1, comm2]).save()
Post.objects().update_one(pull__comments__vote__lt=1)
self.assertEqual(1, len(Post.objects.first().comments))
def test_mapfield_update(self):
"""Ensure that the MapField can be updated."""
class Member(EmbeddedDocument):