Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
1fdc7ce6bb | ||
|
944aa45459 | ||
|
c9842ba13a | ||
|
8840680303 | ||
|
376b9b1316 | ||
|
54bb1cb3d9 | ||
|
43468b474e | ||
|
28a957c684 | ||
|
ec5ddbf391 | ||
|
bab186e195 |
12
.travis.yml
Normal file
12
.travis.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
# http://travis-ci.org/#!/MongoEngine/mongoengine
|
||||
language: python
|
||||
python:
|
||||
- 2.6
|
||||
- 2.7
|
||||
install:
|
||||
- sudo apt-get install zlib1g zlib1g-dev
|
||||
- sudo ln -s /usr/lib/i386-linux-gnu/libz.so /usr/lib/
|
||||
- pip install PIL --use-mirrors ; true
|
||||
- python setup.py install
|
||||
script:
|
||||
- python setup.py test
|
@@ -5,6 +5,9 @@ MongoEngine
|
||||
:Author: Harry Marr (http://github.com/hmarr)
|
||||
:Maintainer: Ross Lawley (http://github.com/rozza)
|
||||
|
||||
.. image:: https://secure.travis-ci.org/MongoEngine/mongoengine.png?branch=master
|
||||
:target: http://travis-ci.org/MongoEngine/mongoengine
|
||||
|
||||
About
|
||||
=====
|
||||
MongoEngine is a Python Object-Document Mapper for working with MongoDB.
|
||||
@@ -96,3 +99,4 @@ 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!
|
||||
|
||||
|
@@ -2,6 +2,16 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
Changes in 0.6.10
|
||||
=================
|
||||
- Fixed basedict / baselist to return super(..)
|
||||
- Promoted BaseDynamicField to DynamicField
|
||||
|
||||
Changes in 0.6.9
|
||||
================
|
||||
- Fixed sparse indexes on inherited docs
|
||||
- Removed FileField auto deletion, needs more work maybe 0.7
|
||||
|
||||
Changes in 0.6.8
|
||||
================
|
||||
- Fixed FileField losing reference when no default set
|
||||
|
@@ -65,12 +65,13 @@ Deleting stored files is achieved with the :func:`delete` method::
|
||||
|
||||
marmot.photo.delete()
|
||||
|
||||
.. note::
|
||||
.. warning::
|
||||
|
||||
The FileField in a Document actually only stores the ID of a file in a
|
||||
separate GridFS collection. This means that `Animal.drop_collection()` will
|
||||
not delete any files. Care should be taken to manually remove associated
|
||||
files before dropping a collection.
|
||||
separate GridFS collection. This means that deleting a document
|
||||
with a defined FileField does not actually delete the file. You must be
|
||||
careful to delete any files in a Document as above before deleting the
|
||||
Document itself.
|
||||
|
||||
|
||||
Replacing files
|
||||
|
@@ -12,7 +12,7 @@ from signals import *
|
||||
__all__ = (document.__all__ + fields.__all__ + connection.__all__ +
|
||||
queryset.__all__ + signals.__all__)
|
||||
|
||||
VERSION = (0, 6, 8)
|
||||
VERSION = (0, 6, 10)
|
||||
|
||||
|
||||
def get_version():
|
||||
|
@@ -435,47 +435,6 @@ class ComplexBaseField(BaseField):
|
||||
owner_document = property(_get_owner_document, _set_owner_document)
|
||||
|
||||
|
||||
class BaseDynamicField(BaseField):
|
||||
"""Used by :class:`~mongoengine.DynamicDocument` to handle dynamic data"""
|
||||
|
||||
def to_mongo(self, value):
|
||||
"""Convert a Python type to a MongoDBcompatible type.
|
||||
"""
|
||||
|
||||
if isinstance(value, basestring):
|
||||
return value
|
||||
|
||||
if hasattr(value, 'to_mongo'):
|
||||
return value.to_mongo()
|
||||
|
||||
if not isinstance(value, (dict, list, tuple)):
|
||||
return value
|
||||
|
||||
is_list = False
|
||||
if not hasattr(value, 'items'):
|
||||
is_list = True
|
||||
value = dict([(k, v) for k, v in enumerate(value)])
|
||||
|
||||
data = {}
|
||||
for k, v in value.items():
|
||||
data[k] = self.to_mongo(v)
|
||||
|
||||
if is_list: # Convert back to a list
|
||||
value = [v for k, v in sorted(data.items(), key=operator.itemgetter(0))]
|
||||
else:
|
||||
value = data
|
||||
return value
|
||||
|
||||
def lookup_member(self, member_name):
|
||||
return member_name
|
||||
|
||||
def prepare_query_value(self, op, value):
|
||||
if isinstance(value, basestring):
|
||||
from mongoengine.fields import StringField
|
||||
return StringField().prepare_query_value(op, value)
|
||||
return self.to_mongo(value)
|
||||
|
||||
|
||||
class ObjectIdField(BaseField):
|
||||
"""An field wrapper around MongoDB's ObjectIds.
|
||||
"""
|
||||
@@ -618,10 +577,6 @@ class DocumentMetaclass(type):
|
||||
raise InvalidDocumentError("Reverse delete rules are not supported for EmbeddedDocuments (field: %s)" % field.name)
|
||||
f.document_type.register_delete_rule(new_class, field.name, delete_rule)
|
||||
|
||||
proxy_class = getattr(field, 'proxy_class', None)
|
||||
if proxy_class is not None:
|
||||
new_class.register_proxy_field(field.name, proxy_class)
|
||||
|
||||
if field.name and hasattr(Document, field.name) and EmbeddedDocument not in new_class.mro():
|
||||
raise InvalidDocumentError("%s is a document method and not a valid field name" % field.name)
|
||||
|
||||
@@ -723,7 +678,6 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
|
||||
'index_opts': {},
|
||||
'queryset_class': QuerySet,
|
||||
'delete_rules': {},
|
||||
'proxy_fields': {},
|
||||
'allow_inheritance': True
|
||||
}
|
||||
|
||||
@@ -864,7 +818,8 @@ class BaseDocument(object):
|
||||
|
||||
field = None
|
||||
if not hasattr(self, name) and not name.startswith('_'):
|
||||
field = BaseDynamicField(db_field=name)
|
||||
from fields import DynamicField
|
||||
field = DynamicField(db_field=name)
|
||||
field.name = name
|
||||
self._dynamic_fields[name] = field
|
||||
|
||||
@@ -1262,15 +1217,15 @@ class BaseList(list):
|
||||
def __init__(self, list_items, instance, name):
|
||||
self._instance = instance
|
||||
self._name = name
|
||||
super(BaseList, self).__init__(list_items)
|
||||
return super(BaseList, self).__init__(list_items)
|
||||
|
||||
def __setitem__(self, *args, **kwargs):
|
||||
self._mark_as_changed()
|
||||
super(BaseList, self).__setitem__(*args, **kwargs)
|
||||
return super(BaseList, self).__setitem__(*args, **kwargs)
|
||||
|
||||
def __delitem__(self, *args, **kwargs):
|
||||
self._mark_as_changed()
|
||||
super(BaseList, self).__delitem__(*args, **kwargs)
|
||||
return super(BaseList, self).__delitem__(*args, **kwargs)
|
||||
|
||||
def __getstate__(self):
|
||||
self.observer = None
|
||||
@@ -1324,23 +1279,23 @@ class BaseDict(dict):
|
||||
def __init__(self, dict_items, instance, name):
|
||||
self._instance = instance
|
||||
self._name = name
|
||||
super(BaseDict, self).__init__(dict_items)
|
||||
return super(BaseDict, self).__init__(dict_items)
|
||||
|
||||
def __setitem__(self, *args, **kwargs):
|
||||
self._mark_as_changed()
|
||||
super(BaseDict, self).__setitem__(*args, **kwargs)
|
||||
return super(BaseDict, self).__setitem__(*args, **kwargs)
|
||||
|
||||
def __delete__(self, *args, **kwargs):
|
||||
self._mark_as_changed()
|
||||
super(BaseDict, self).__delete__(*args, **kwargs)
|
||||
return super(BaseDict, self).__delete__(*args, **kwargs)
|
||||
|
||||
def __delitem__(self, *args, **kwargs):
|
||||
self._mark_as_changed()
|
||||
super(BaseDict, self).__delitem__(*args, **kwargs)
|
||||
return super(BaseDict, self).__delitem__(*args, **kwargs)
|
||||
|
||||
def __delattr__(self, *args, **kwargs):
|
||||
self._mark_as_changed()
|
||||
super(BaseDict, self).__delattr__(*args, **kwargs)
|
||||
return super(BaseDict, self).__delattr__(*args, **kwargs)
|
||||
|
||||
def __getstate__(self):
|
||||
self.instance = None
|
||||
@@ -1353,19 +1308,19 @@ class BaseDict(dict):
|
||||
|
||||
def clear(self, *args, **kwargs):
|
||||
self._mark_as_changed()
|
||||
super(BaseDict, self).clear(*args, **kwargs)
|
||||
return super(BaseDict, self).clear(*args, **kwargs)
|
||||
|
||||
def pop(self, *args, **kwargs):
|
||||
self._mark_as_changed()
|
||||
super(BaseDict, self).pop(*args, **kwargs)
|
||||
return super(BaseDict, self).pop(*args, **kwargs)
|
||||
|
||||
def popitem(self, *args, **kwargs):
|
||||
self._mark_as_changed()
|
||||
super(BaseDict, self).popitem(*args, **kwargs)
|
||||
return super(BaseDict, self).popitem(*args, **kwargs)
|
||||
|
||||
def update(self, *args, **kwargs):
|
||||
self._mark_as_changed()
|
||||
super(BaseDict, self).update(*args, **kwargs)
|
||||
return super(BaseDict, self).update(*args, **kwargs)
|
||||
|
||||
def _mark_as_changed(self):
|
||||
if hasattr(self._instance, '_mark_as_changed'):
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import pymongo
|
||||
|
||||
from bson.dbref import DBRef
|
||||
|
||||
from mongoengine import signals
|
||||
@@ -278,11 +279,6 @@ class Document(BaseDocument):
|
||||
signals.pre_delete.send(self.__class__, document=self)
|
||||
|
||||
try:
|
||||
for field_name in self._meta['proxy_fields']:
|
||||
proxy_class = self._meta['proxy_fields'][field_name]
|
||||
if hasattr(proxy_class, 'delete'):
|
||||
proxy = getattr(self, field_name)
|
||||
proxy.delete()
|
||||
self.__class__.objects(pk=self.pk).delete(safe=safe)
|
||||
except pymongo.errors.OperationFailure, err:
|
||||
message = u'Could not delete document (%s)' % err.message
|
||||
@@ -347,13 +343,6 @@ class Document(BaseDocument):
|
||||
"""
|
||||
cls._meta['delete_rules'][(document_cls, field_name)] = rule
|
||||
|
||||
@classmethod
|
||||
def register_proxy_field(cls, field_name, proxy_class):
|
||||
"""This method registers fields with proxy classes to delete them when
|
||||
removing this object.
|
||||
"""
|
||||
cls._meta['proxy_fields'][field_name] = proxy_class
|
||||
|
||||
@classmethod
|
||||
def drop_collection(cls):
|
||||
"""Drops the entire collection associated with this
|
||||
@@ -371,7 +360,7 @@ class DynamicDocument(Document):
|
||||
way as an ordinary document but has expando style properties. Any data
|
||||
passed or set against the :class:`~mongoengine.DynamicDocument` that is
|
||||
not a field is automatically converted into a
|
||||
:class:`~mongoengine.BaseDynamicField` and data can be attributed to that
|
||||
:class:`~mongoengine.DynamicField` and data can be attributed to that
|
||||
field.
|
||||
|
||||
..note::
|
||||
|
@@ -30,7 +30,7 @@ except ImportError:
|
||||
__all__ = ['StringField', 'IntField', 'FloatField', 'BooleanField',
|
||||
'DateTimeField', 'EmbeddedDocumentField', 'ListField', 'DictField',
|
||||
'ObjectIdField', 'ReferenceField', 'ValidationError', 'MapField',
|
||||
'DecimalField', 'ComplexDateTimeField', 'URLField',
|
||||
'DecimalField', 'ComplexDateTimeField', 'URLField', 'DynamicField',
|
||||
'GenericReferenceField', 'FileField', 'BinaryField',
|
||||
'SortedListField', 'EmailField', 'GeoPointField', 'ImageField',
|
||||
'SequenceField', 'UUIDField', 'GenericEmbeddedDocumentField']
|
||||
@@ -473,6 +473,47 @@ class GenericEmbeddedDocumentField(BaseField):
|
||||
return data
|
||||
|
||||
|
||||
class DynamicField(BaseField):
|
||||
"""Used by :class:`~mongoengine.DynamicDocument` to handle dynamic data"""
|
||||
|
||||
def to_mongo(self, value):
|
||||
"""Convert a Python type to a MongoDBcompatible type.
|
||||
"""
|
||||
|
||||
if isinstance(value, basestring):
|
||||
return value
|
||||
|
||||
if hasattr(value, 'to_mongo'):
|
||||
return value.to_mongo()
|
||||
|
||||
if not isinstance(value, (dict, list, tuple)):
|
||||
return value
|
||||
|
||||
is_list = False
|
||||
if not hasattr(value, 'items'):
|
||||
is_list = True
|
||||
value = dict([(k, v) for k, v in enumerate(value)])
|
||||
|
||||
data = {}
|
||||
for k, v in value.items():
|
||||
data[k] = self.to_mongo(v)
|
||||
|
||||
if is_list: # Convert back to a list
|
||||
value = [v for k, v in sorted(data.items(), key=itemgetter(0))]
|
||||
else:
|
||||
value = data
|
||||
return value
|
||||
|
||||
def lookup_member(self, member_name):
|
||||
return member_name
|
||||
|
||||
def prepare_query_value(self, op, value):
|
||||
if isinstance(value, basestring):
|
||||
from mongoengine.fields import StringField
|
||||
return StringField().prepare_query_value(op, value)
|
||||
return self.to_mongo(value)
|
||||
|
||||
|
||||
class ListField(ComplexBaseField):
|
||||
"""A list field that wraps a standard field, allowing multiple instances
|
||||
of the field to be used as a list in the database.
|
||||
|
@@ -512,6 +512,10 @@ class QuerySet(object):
|
||||
key = '.'.join(parts)
|
||||
index_list.append((key, direction))
|
||||
|
||||
# If sparse - dont include types
|
||||
if spec.get('sparse', False):
|
||||
use_types = False
|
||||
|
||||
# Check if a list field is being used, don't use _types if it is
|
||||
if use_types and not all(f._index_with_types for f in fields):
|
||||
use_types = False
|
||||
@@ -623,8 +627,8 @@ class QuerySet(object):
|
||||
if field_name in document._fields:
|
||||
field = document._fields[field_name]
|
||||
elif document._dynamic:
|
||||
from base import BaseDynamicField
|
||||
field = BaseDynamicField(db_field=field_name)
|
||||
from fields import DynamicField
|
||||
field = DynamicField(db_field=field_name)
|
||||
else:
|
||||
raise InvalidQueryError('Cannot resolve field "%s"'
|
||||
% field_name)
|
||||
|
@@ -5,7 +5,7 @@
|
||||
%define srcname mongoengine
|
||||
|
||||
Name: python-%{srcname}
|
||||
Version: 0.6.8
|
||||
Version: 0.6.10
|
||||
Release: 1%{?dist}
|
||||
Summary: A Python Document-Object Mapper for working with MongoDB
|
||||
|
||||
|
@@ -1620,38 +1620,6 @@ class FieldTest(unittest.TestCase):
|
||||
file = FileField()
|
||||
DemoFile.objects.create()
|
||||
|
||||
def test_file_delete_cleanup(self):
|
||||
"""Ensure that the gridfs file is deleted when a document
|
||||
with a GridFSProxied Field is deleted"""
|
||||
class TestFile(Document):
|
||||
file = FileField()
|
||||
|
||||
class TestImage(Document):
|
||||
image = ImageField()
|
||||
|
||||
TestFile.drop_collection()
|
||||
|
||||
testfile = TestFile()
|
||||
testfile.file.put('Hello, World!')
|
||||
testfile.save()
|
||||
|
||||
testfile_grid_id = testfile.file.grid_id
|
||||
testfile_fs = testfile.file.fs
|
||||
|
||||
testfile.delete()
|
||||
self.assertFalse(testfile_fs.exists(testfile_grid_id))
|
||||
|
||||
TestImage.drop_collection()
|
||||
|
||||
testimage = TestImage()
|
||||
testimage.image.put(open(TEST_IMAGE_PATH, 'r'))
|
||||
testimage.save()
|
||||
|
||||
testimage_grid_id = testimage.image.grid_id
|
||||
testimage_fs = testimage.image.fs
|
||||
|
||||
testimage.delete()
|
||||
self.assertFalse(testimage_fs.exists(testimage_grid_id))
|
||||
|
||||
def test_file_field_no_default(self):
|
||||
|
||||
|
Reference in New Issue
Block a user