Merge branch 'master' into pr/585
This commit is contained in:
		| @@ -13,8 +13,7 @@ from mongoengine import signals | ||||
| from mongoengine.common import _import_class | ||||
| from mongoengine.errors import (ValidationError, InvalidDocumentError, | ||||
|                                 LookUpError) | ||||
| from mongoengine.python_support import (PY3, UNICODE_KWARGS, txt_type, | ||||
|                                         to_str_keys_recursive) | ||||
| from mongoengine.python_support import PY3, txt_type | ||||
|  | ||||
| from mongoengine.base.common import get_document, ALLOW_INHERITANCE | ||||
| from mongoengine.base.datastructures import BaseDict, BaseList | ||||
| @@ -545,10 +544,6 @@ class BaseDocument(object): | ||||
|         # class if unavailable | ||||
|         class_name = son.get('_cls', cls._class_name) | ||||
|         data = dict(("%s" % key, value) for key, value in son.iteritems()) | ||||
|         if not UNICODE_KWARGS: | ||||
|             # python 2.6.4 and lower cannot handle unicode keys | ||||
|             # passed to class constructor example: cls(**data) | ||||
|             to_str_keys_recursive(data) | ||||
|  | ||||
|         # Return correct subclass for document type | ||||
|         if class_name != cls._class_name: | ||||
|   | ||||
| @@ -93,20 +93,11 @@ def get_connection(alias=DEFAULT_CONNECTION_NAME, reconnect=False): | ||||
|             raise ConnectionError(msg) | ||||
|         conn_settings = _connection_settings[alias].copy() | ||||
|  | ||||
|         if hasattr(pymongo, 'version_tuple'):  # Support for 2.1+ | ||||
|             conn_settings.pop('name', None) | ||||
|             conn_settings.pop('slaves', None) | ||||
|             conn_settings.pop('is_slave', None) | ||||
|             conn_settings.pop('username', None) | ||||
|             conn_settings.pop('password', None) | ||||
|         else: | ||||
|             # Get all the slave connections | ||||
|             if 'slaves' in conn_settings: | ||||
|                 slaves = [] | ||||
|                 for slave_alias in conn_settings['slaves']: | ||||
|                     slaves.append(get_connection(slave_alias)) | ||||
|                 conn_settings['slaves'] = slaves | ||||
|                 conn_settings.pop('read_preference', None) | ||||
|         conn_settings.pop('name', None) | ||||
|         conn_settings.pop('slaves', None) | ||||
|         conn_settings.pop('is_slave', None) | ||||
|         conn_settings.pop('username', None) | ||||
|         conn_settings.pop('password', None) | ||||
|  | ||||
|         connection_class = MongoClient | ||||
|         if 'replicaSet' in conn_settings: | ||||
| @@ -119,7 +110,19 @@ def get_connection(alias=DEFAULT_CONNECTION_NAME, reconnect=False): | ||||
|             connection_class = MongoReplicaSetClient | ||||
|  | ||||
|         try: | ||||
|             _connections[alias] = connection_class(**conn_settings) | ||||
|             connection = None | ||||
|             connection_settings_iterator = ((alias, settings.copy()) for alias, settings in _connection_settings.iteritems()) | ||||
|             for alias, connection_settings in connection_settings_iterator: | ||||
|                 connection_settings.pop('name', None) | ||||
|                 connection_settings.pop('slaves', None) | ||||
|                 connection_settings.pop('is_slave', None) | ||||
|                 connection_settings.pop('username', None) | ||||
|                 connection_settings.pop('password', None) | ||||
|                 if conn_settings == connection_settings and _connections.get(alias, None): | ||||
|                     connection = _connections[alias] | ||||
|                     break | ||||
|  | ||||
|             _connections[alias] = connection if connection else connection_class(**conn_settings) | ||||
|         except Exception, e: | ||||
|             raise ConnectionError("Cannot connect to database %s :\n%s" % (alias, e)) | ||||
|     return _connections[alias] | ||||
|   | ||||
| @@ -391,7 +391,7 @@ class DateTimeField(BaseField): | ||||
|         if dateutil: | ||||
|             try: | ||||
|                 return dateutil.parser.parse(value) | ||||
|             except ValueError: | ||||
|             except (TypeError, ValueError): | ||||
|                 return None | ||||
|  | ||||
|         # split usecs, because they are not recognized by strptime. | ||||
| @@ -760,7 +760,7 @@ class DictField(ComplexBaseField): | ||||
|     similar to an embedded document, but the structure is not defined. | ||||
|  | ||||
|     .. note:: | ||||
|         Required means it cannot be empty - as the default for ListFields is [] | ||||
|         Required means it cannot be empty - as the default for DictFields is {} | ||||
|  | ||||
|     .. versionadded:: 0.3 | ||||
|     .. versionchanged:: 0.5 - Can now handle complex / varying types of data | ||||
| @@ -1613,7 +1613,12 @@ class UUIDField(BaseField): | ||||
|  | ||||
|  | ||||
| class GeoPointField(BaseField): | ||||
|     """A list storing a latitude and longitude. | ||||
|     """A list storing a longitude and latitude coordinate.  | ||||
|  | ||||
|     .. note:: this represents a generic point in a 2D plane and a legacy way of  | ||||
|         representing a geo point. It admits 2d indexes but not "2dsphere" indexes  | ||||
|         in MongoDB > 2.4 which are more natural for modeling geospatial points.  | ||||
|         See :ref:`geospatial-indexes`  | ||||
|  | ||||
|     .. versionadded:: 0.4 | ||||
|     """ | ||||
| @@ -1635,7 +1640,7 @@ class GeoPointField(BaseField): | ||||
|  | ||||
|  | ||||
| class PointField(GeoJsonBaseField): | ||||
|     """A geo json field storing a latitude and longitude. | ||||
|     """A GeoJSON field storing a longitude and latitude coordinate. | ||||
|  | ||||
|     The data is represented as: | ||||
|  | ||||
| @@ -1654,7 +1659,7 @@ class PointField(GeoJsonBaseField): | ||||
|  | ||||
|  | ||||
| class LineStringField(GeoJsonBaseField): | ||||
|     """A geo json field storing a line of latitude and longitude coordinates. | ||||
|     """A GeoJSON field storing a line of longitude and latitude coordinates. | ||||
|  | ||||
|     The data is represented as: | ||||
|  | ||||
| @@ -1672,7 +1677,7 @@ class LineStringField(GeoJsonBaseField): | ||||
|  | ||||
|  | ||||
| class PolygonField(GeoJsonBaseField): | ||||
|     """A geo json field storing a polygon of latitude and longitude coordinates. | ||||
|     """A GeoJSON field storing a polygon of longitude and latitude coordinates. | ||||
|  | ||||
|     The data is represented as: | ||||
|  | ||||
|   | ||||
| @@ -3,8 +3,6 @@ | ||||
| import sys | ||||
|  | ||||
| PY3 = sys.version_info[0] == 3 | ||||
| PY25 = sys.version_info[:2] == (2, 5) | ||||
| UNICODE_KWARGS = int(''.join([str(x) for x in sys.version_info[:3]])) > 264 | ||||
|  | ||||
| if PY3: | ||||
|     import codecs | ||||
| @@ -29,33 +27,3 @@ else: | ||||
|     txt_type = unicode | ||||
|  | ||||
| str_types = (bin_type, txt_type) | ||||
|  | ||||
| if PY25: | ||||
|     def product(*args, **kwds): | ||||
|         pools = map(tuple, args) * kwds.get('repeat', 1) | ||||
|         result = [[]] | ||||
|         for pool in pools: | ||||
|             result = [x + [y] for x in result for y in pool] | ||||
|         for prod in result: | ||||
|             yield tuple(prod) | ||||
|     reduce = reduce | ||||
| else: | ||||
|     from itertools import product | ||||
|     from functools import reduce | ||||
|  | ||||
|  | ||||
| # For use with Python 2.5 | ||||
| # converts all keys from unicode to str for d and all nested dictionaries | ||||
| def to_str_keys_recursive(d): | ||||
|     if isinstance(d, list): | ||||
|         for val in d: | ||||
|             if isinstance(val, (dict, list)): | ||||
|                 to_str_keys_recursive(val) | ||||
|     elif isinstance(d, dict): | ||||
|         for key, val in d.items(): | ||||
|             if isinstance(val, (dict, list)): | ||||
|                 to_str_keys_recursive(val) | ||||
|             if isinstance(key, unicode): | ||||
|                 d[str(key)] = d.pop(key) | ||||
|     else: | ||||
|         raise ValueError("non list/dict parameter not allowed") | ||||
|   | ||||
| @@ -10,6 +10,7 @@ import warnings | ||||
| from bson.code import Code | ||||
| from bson import json_util | ||||
| import pymongo | ||||
| import pymongo.errors | ||||
| from pymongo.common import validate_read_preference | ||||
|  | ||||
| from mongoengine import signals | ||||
| @@ -50,7 +51,7 @@ class BaseQuerySet(object): | ||||
|         self._initial_query = {} | ||||
|         self._where_clause = None | ||||
|         self._loaded_fields = QueryFieldList() | ||||
|         self._ordering = [] | ||||
|         self._ordering = None | ||||
|         self._snapshot = False | ||||
|         self._timeout = True | ||||
|         self._class_check = True | ||||
| @@ -154,6 +155,22 @@ class BaseQuerySet(object): | ||||
|     def __iter__(self): | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def _has_data(self): | ||||
|         """ Retrieves whether cursor has any data. """ | ||||
|  | ||||
|         queryset = self.order_by() | ||||
|         return False if queryset.first() is None else True | ||||
|  | ||||
|     def __nonzero__(self): | ||||
|         """ Avoid to open all records in an if stmt in Py2. """ | ||||
|  | ||||
|         return self._has_data() | ||||
|  | ||||
|     def __bool__(self): | ||||
|         """ Avoid to open all records in an if stmt in Py3. """ | ||||
|  | ||||
|         return self._has_data() | ||||
|  | ||||
|     # Core functions | ||||
|  | ||||
|     def all(self): | ||||
| @@ -443,6 +460,8 @@ class BaseQuerySet(object): | ||||
|                 return result | ||||
|             elif result: | ||||
|                 return result['n'] | ||||
|         except pymongo.errors.DuplicateKeyError, err: | ||||
|             raise NotUniqueError(u'Update failed (%s)' % unicode(err)) | ||||
|         except pymongo.errors.OperationFailure, err: | ||||
|             if unicode(err) == u'multi not coded yet': | ||||
|                 message = u'update() method requires MongoDB 1.1.3+' | ||||
| @@ -466,6 +485,59 @@ class BaseQuerySet(object): | ||||
|         return self.update( | ||||
|             upsert=upsert, multi=False, write_concern=write_concern, **update) | ||||
|  | ||||
|     def modify(self, upsert=False, full_response=False, remove=False, new=False, **update): | ||||
|         """Update and return the updated document. | ||||
|  | ||||
|         Returns either the document before or after modification based on `new` | ||||
|         parameter. If no documents match the query and `upsert` is false, | ||||
|         returns ``None``. If upserting and `new` is false, returns ``None``. | ||||
|  | ||||
|         If the full_response parameter is ``True``, the return value will be | ||||
|         the entire response object from the server, including the 'ok' and | ||||
|         'lastErrorObject' fields, rather than just the modified document. | ||||
|         This is useful mainly because the 'lastErrorObject' document holds | ||||
|         information about the command's execution. | ||||
|  | ||||
|         :param upsert: insert if document doesn't exist (default ``False``) | ||||
|         :param full_response: return the entire response object from the | ||||
|             server (default ``False``) | ||||
|         :param remove: remove rather than updating (default ``False``) | ||||
|         :param new: return updated rather than original document | ||||
|             (default ``False``) | ||||
|         :param update: Django-style update keyword arguments | ||||
|  | ||||
|         .. versionadded:: 0.9 | ||||
|         """ | ||||
|  | ||||
|         if remove and new: | ||||
|             raise OperationError("Conflicting parameters: remove and new") | ||||
|  | ||||
|         if not update and not upsert and not remove: | ||||
|             raise OperationError("No update parameters, must either update or remove") | ||||
|  | ||||
|         queryset = self.clone() | ||||
|         query = queryset._query | ||||
|         update = transform.update(queryset._document, **update) | ||||
|         sort = queryset._ordering | ||||
|  | ||||
|         try: | ||||
|             result = queryset._collection.find_and_modify( | ||||
|                 query, update, upsert=upsert, sort=sort, remove=remove, new=new, | ||||
|                 full_response=full_response, **self._cursor_args) | ||||
|         except pymongo.errors.DuplicateKeyError, err: | ||||
|             raise NotUniqueError(u"Update failed (%s)" % err) | ||||
|         except pymongo.errors.OperationFailure, err: | ||||
|             raise OperationError(u"Update failed (%s)" % err) | ||||
|  | ||||
|         if full_response: | ||||
|             if result["value"] is not None: | ||||
|                 result["value"] = self._document._from_son(result["value"]) | ||||
|         else: | ||||
|             if result is not None: | ||||
|                 result = self._document._from_son(result) | ||||
|  | ||||
|         return result | ||||
|  | ||||
|     def with_id(self, object_id): | ||||
|         """Retrieve the object matching the id provided.  Uses `object_id` only | ||||
|         and raises InvalidQueryError if a filter has been applied. Returns | ||||
| @@ -1189,8 +1261,9 @@ class BaseQuerySet(object): | ||||
|             if self._ordering: | ||||
|                 # Apply query ordering | ||||
|                 self._cursor_obj.sort(self._ordering) | ||||
|             elif self._document._meta['ordering']: | ||||
|                 # Otherwise, apply the ordering from the document model | ||||
|             elif self._ordering is None and self._document._meta['ordering']: | ||||
|                 # Otherwise, apply the ordering from the document model, unless | ||||
|                 # it's been explicitly cleared via order_by with no arguments | ||||
|                 order = self._get_order_by(self._document._meta['ordering']) | ||||
|                 self._cursor_obj.sort(order) | ||||
|  | ||||
| @@ -1392,7 +1465,7 @@ class BaseQuerySet(object): | ||||
|                 pass | ||||
|             key_list.append((key, direction)) | ||||
|  | ||||
|         if self._cursor_obj: | ||||
|         if self._cursor_obj and key_list: | ||||
|             self._cursor_obj.sort(key_list) | ||||
|         return key_list | ||||
|  | ||||
|   | ||||
| @@ -38,7 +38,7 @@ def query(_doc_cls=None, _field_operation=False, **query): | ||||
|             mongo_query.update(value) | ||||
|             continue | ||||
|  | ||||
|         parts = key.split('__') | ||||
|         parts = key.rsplit('__') | ||||
|         indices = [(i, p) for i, p in enumerate(parts) if p.isdigit()] | ||||
|         parts = [part for part in parts if not part.isdigit()] | ||||
|         # Check for an operator and transform to mongo-style if there is | ||||
|   | ||||
| @@ -1,8 +1,9 @@ | ||||
| import copy | ||||
|  | ||||
| from mongoengine.errors import InvalidQueryError | ||||
| from mongoengine.python_support import product, reduce | ||||
| from itertools import product | ||||
| from functools import reduce | ||||
|  | ||||
| from mongoengine.errors import InvalidQueryError | ||||
| from mongoengine.queryset import transform | ||||
|  | ||||
| __all__ = ('Q',) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user