Compare commits
	
		
			120 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 00c8d7e6f5 | ||
|  | 0d89e967f2 | ||
|  | 447f8d0113 | ||
|  | 60802796cb | ||
|  | 5b42578cb1 | ||
|  | 25a0a5364a | ||
|  | 047cc218a6 | ||
|  | 39fc862676 | ||
|  | f47d926f29 | ||
|  | f4d0938e3d | ||
|  | f156da4ec2 | ||
|  | 0c1e5da9a8 | ||
|  | d6b317c552 | ||
|  | 01826c6876 | ||
|  | 0b62c9d2f6 | ||
|  | 72161a9b71 | ||
|  | df8f4e7251 | ||
|  | aa13ab37c4 | ||
|  | acda64a837 | ||
|  | 49a001a93a | ||
|  | 22a6ec7794 | ||
|  | 26c6e4997c | ||
|  | d7086fc4a3 | ||
|  | 92150e07d3 | ||
|  | ac3c857e1a | ||
|  | 48e313fb44 | ||
|  | 5390117275 | ||
|  | 0b3af2052f | ||
|  | bb19ba3eb6 | ||
|  | 879bf08d18 | ||
|  | b99421e7ee | ||
|  | 3b6d8fab47 | ||
|  | 53c0cdc0c1 | ||
|  | 58f877de1a | ||
|  | 95a7b33fb4 | ||
|  | 81dd5adccf | ||
|  | 94e86a0be1 | ||
|  | 5b2dbfe007 | ||
|  | 4451843a39 | ||
|  | 5e2c5fa97b | ||
|  | 018b206177 | ||
|  | 03d31b1890 | ||
|  | 265776566e | ||
|  | 6e77e32855 | ||
|  | 0b1c506626 | ||
|  | 719a653375 | ||
|  | 66520c77f8 | ||
|  | ab2d019349 | ||
|  | d0e0b291df | ||
|  | 200e9eca92 | ||
|  | 634f771547 | ||
|  | 2996f8919d | ||
|  | 1b68efe7c7 | ||
|  | a19a7b976c | ||
|  | 145b0c33fc | ||
|  | 8b1a39f2c1 | ||
|  | 6dbc051409 | ||
|  | c148a5bbfc | ||
|  | 90d9bd9723 | ||
|  | bc7e6ccf53 | ||
|  | 6cab002214 | ||
|  | 3762a69537 | ||
|  | 348f7b5dfc | ||
|  | 008a62e4e9 | ||
|  | a4c5fa57e0 | ||
|  | 9be6c41af7 | ||
|  | 5c311eefb1 | ||
|  | d0ceb74a2e | ||
|  | ea1fe6a538 | ||
|  | a93509c9b3 | ||
|  | 210e9e23af | ||
|  | c4513f0286 | ||
|  | 1114572b47 | ||
|  | b2588d1c4f | ||
|  | 69d3e0c4b6 | ||
|  | e2414d8fea | ||
|  | 24db0d1499 | ||
|  | 89f505bb13 | ||
|  | df5b1f3806 | ||
|  | 755deb3ffe | ||
|  | 59f8c9f38e | ||
|  | 69e9b5d55e | ||
|  | a2d8b0ffbe | ||
|  | 0bbf3a3d76 | ||
|  | 10de19d38b | ||
|  | 73aff806f3 | ||
|  | 963a223e7e | ||
|  | bbfc2f416e | ||
|  | e05d31eaaf | ||
|  | 431f006751 | ||
|  | ffc9d7b152 | ||
|  | 79604180db | ||
|  | 7d6e117f68 | ||
|  | b3cc2f990a | ||
|  | 8d953f0bcb | ||
|  | 5cac52720c | ||
|  | bca6119db8 | ||
|  | 568000805f | ||
|  | 3fb6307596 | ||
|  | 7aa0031dec | ||
|  | 2585f1b724 | ||
|  | 470e08f616 | ||
|  | f1e51f9708 | ||
|  | e0becc109d | ||
|  | 47e4dd40cd | ||
|  | c38faebc25 | ||
|  | 21b7d8f8ea | ||
|  | 3357b55fbf | ||
|  | f01add9ef5 | ||
|  | b0b8e11c60 | ||
|  | 7e0fcb9e65 | ||
|  | 972235cf06 | ||
|  | b3c9a76619 | ||
|  | 5f84d6f8f8 | ||
|  | 1cdeb8130d | ||
|  | ce69428cc6 | ||
|  | 1818cf7114 | ||
|  | b375c41586 | ||
|  | d85ee4e051 | ||
|  | cfc394963f | 
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -2,3 +2,6 @@ | ||||
| .*.swp | ||||
| docs/.build | ||||
| docs/_build | ||||
| build/ | ||||
| dist/ | ||||
| mongoengine.egg-info/ | ||||
							
								
								
									
										4
									
								
								AUTHORS
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								AUTHORS
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| Harry Marr <harry@hmarr.com> | ||||
| Matt Dennewitz <mattdennewitz@gmail.com> | ||||
| Deepak Thukral <iapain@yahoo.com> | ||||
| Florian Schlachter <flori@n-schlachter.de> | ||||
| @@ -1,6 +1,6 @@ | ||||
| include MANIFEST.in | ||||
| include README.rst | ||||
| include LICENSE | ||||
| include AUTHORS | ||||
| recursive-include docs * | ||||
| prune docs/_build/* | ||||
| recursive-include tests * | ||||
| recursive-exclude * *.pyc *.swp | ||||
| prune docs/_build | ||||
|   | ||||
							
								
								
									
										10
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.rst
									
									
									
									
									
								
							| @@ -15,7 +15,7 @@ a `tutorial <http://hmarr.com/mongoengine/tutorial.html>`_, a `user guide | ||||
| Installation | ||||
| ============ | ||||
| If you have `setuptools <http://peak.telecommunity.com/DevCenter/setuptools>`_ | ||||
| you can use ``easy_install mongoengine``. Otherwise, you can download the | ||||
| you can use ``easy_install -U mongoengine``. Otherwise, you can download the | ||||
| source from `GitHub <http://github.com/hmarr/mongoengine>`_ and run ``python | ||||
| setup.py install``. | ||||
|  | ||||
| @@ -82,6 +82,14 @@ Tests | ||||
| To run the test suite, ensure you are running a local instance of MongoDB on | ||||
| the standard port, and run ``python setup.py test``. | ||||
|  | ||||
| Community | ||||
| ========= | ||||
| - `MongoEngine Users mailing list  | ||||
|   <http://groups.google.com/group/mongoengine-users>`_ | ||||
| - `MongoEngine Developers mailing list  | ||||
|   <http://groups.google.com/group/mongoengine-dev>`_ | ||||
| - `#mongoengine IRC channel <irc://irc.freenode.net/mongoengine>`_ | ||||
|  | ||||
| Contributing | ||||
| ============ | ||||
| The source is available on `GitHub <http://github.com/hmarr/mongoengine>`_ - to | ||||
|   | ||||
| @@ -21,6 +21,9 @@ Documents | ||||
| .. autoclass:: mongoengine.EmbeddedDocument | ||||
|    :members: | ||||
|     | ||||
| .. autoclass:: mongoengine.document.MapReduceDocument | ||||
|   :members: | ||||
|  | ||||
| Querying | ||||
| ======== | ||||
|  | ||||
| @@ -36,18 +39,28 @@ Fields | ||||
|  | ||||
| .. autoclass:: mongoengine.StringField | ||||
|  | ||||
| .. autoclass:: mongoengine.URLField | ||||
|  | ||||
| .. autoclass:: mongoengine.IntField | ||||
|  | ||||
| .. autoclass:: mongoengine.FloatField | ||||
|  | ||||
| .. autoclass:: mongoengine.DecimalField | ||||
|  | ||||
| .. autoclass:: mongoengine.BooleanField | ||||
|  | ||||
| .. autoclass:: mongoengine.DateTimeField | ||||
|  | ||||
| .. autoclass:: mongoengine.EmbeddedDocumentField | ||||
|  | ||||
| .. autoclass:: mongoengine.DictField | ||||
|  | ||||
| .. autoclass:: mongoengine.ListField | ||||
|  | ||||
| .. autoclass:: mongoengine.BinaryField | ||||
|  | ||||
| .. autoclass:: mongoengine.ObjectIdField | ||||
|  | ||||
| .. autoclass:: mongoengine.ReferenceField | ||||
|  | ||||
| .. autoclass:: mongoengine.GenericReferenceField | ||||
|   | ||||
| @@ -2,25 +2,66 @@ | ||||
| Changelog | ||||
| ========= | ||||
|  | ||||
| Changes in v0.3 | ||||
| =============== | ||||
| - Added MapReduce support | ||||
| - Added ``contains``, ``startswith`` and ``endswith`` query operators (and | ||||
|   case-insensitive versions that are prefixed with 'i')  | ||||
| - Deprecated fields' ``name`` parameter, replaced with ``db_field`` | ||||
| - Added ``QuerySet.only`` for only retrieving specific fields | ||||
| - Added ``QuerySet.in_bulk()`` for bulk querying using ids | ||||
| - ``QuerySet``\ s now have a ``rewind()`` method, which is called automatically | ||||
|   when the iterator is exhausted, allowing ``QuerySet``\ s to be reused | ||||
| - Added ``DictField`` | ||||
| - Added ``URLField`` | ||||
| - Added ``DecimalField`` | ||||
| - Added ``BinaryField`` | ||||
| - Added ``GenericReferenceField`` | ||||
| - Added ``get()`` and ``get_or_create()`` methods to ``QuerySet`` | ||||
| - ``ReferenceField``\ s may now reference the document they are defined on | ||||
|   (recursive references) and documents that have not yet been defined | ||||
| - ``Document`` objects may now be compared for equality (equal if _ids are | ||||
|   equal and documents are of same type) | ||||
| - ``QuerySet`` update methods now have an ``upsert`` parameter | ||||
| - Added field name substitution for Javascript code (allows the user to use the | ||||
|   Python names for fields in JS, which are later substituted for the real field | ||||
|   names) | ||||
| - ``Q`` objects now support regex querying | ||||
| - Fixed bug where referenced documents within lists weren't properly | ||||
|   dereferenced | ||||
| - ``ReferenceField``\ s may now be queried using their _id | ||||
| - Fixed bug where ``EmbeddedDocuments`` couldn't be non-polymorphic | ||||
| - ``queryset_manager`` functions now accept two arguments -- the document class | ||||
|   as the first and the queryset as the second | ||||
| - Fixed bug where ``QuerySet.exec_js`` ignored ``Q`` objects | ||||
| - Other minor fixes | ||||
|  | ||||
| Changes in v0.2.2 | ||||
| ================= | ||||
| - Fixed bug that prevented indexes from being used on ``ListField``\ s | ||||
| - ``Document.filter()`` added as an alias to ``Document.__call__()`` | ||||
| - ``validate()`` may now be used on ``EmbeddedDocument``\ s | ||||
|  | ||||
| Changes in v0.2.1 | ||||
| ================= | ||||
| - Added a MongoEngine backend for Django sessions | ||||
| - Added force_insert to Document.save() | ||||
| - Improved querying syntax for ListField and EmbeddedDocumentField | ||||
| - Added support for user-defined primary keys (_ids in MongoDB) | ||||
| - Added ``force_insert`` to ``Document.save()`` | ||||
| - Improved querying syntax for ``ListField`` and ``EmbeddedDocumentField`` | ||||
| - Added support for user-defined primary keys (``_id`` in MongoDB) | ||||
|  | ||||
| Changes in v0.2 | ||||
| =============== | ||||
| - Added Q class for building advanced queries | ||||
| - Added QuerySet methods for atomic updates to documents | ||||
| - Fields may now specify ``unique=True`` to enforce uniqueness across a collection | ||||
| - Added ``Q`` class for building advanced queries | ||||
| - Added ``QuerySet`` methods for atomic updates to documents | ||||
| - Fields may now specify ``unique=True`` to enforce uniqueness across a  | ||||
|   collection | ||||
| - Added option for default document ordering | ||||
| - Fixed bug in index definitions | ||||
|  | ||||
| Changes in v0.1.3 | ||||
| ================= | ||||
| - Added Django authentication backend | ||||
| - Added Document.meta support for indexes, which are ensured just before  | ||||
| - Added ``Document.meta`` support for indexes, which are ensured just before  | ||||
|   querying takes place | ||||
| - A few minor bugfixes | ||||
|  | ||||
| @@ -30,8 +71,8 @@ Changes in v0.1.2 | ||||
| - Query values may be processed before before being used in queries | ||||
| - Made connections lazy | ||||
| - Fixed bug in Document dictionary-style access | ||||
| - Added BooleanField | ||||
| - Added Document.reload method | ||||
| - Added ``BooleanField`` | ||||
| - Added ``Document.reload()`` method | ||||
|  | ||||
|  | ||||
| Changes in v0.1.1 | ||||
|   | ||||
| @@ -22,7 +22,7 @@ sys.path.append(os.path.abspath('..')) | ||||
|  | ||||
| # Add any Sphinx extension module names here, as strings. They can be extensions | ||||
| # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. | ||||
| extensions = ['sphinx.ext.autodoc'] | ||||
| extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo'] | ||||
|  | ||||
| # Add any paths that contain templates here, relative to this directory. | ||||
| templates_path = ['_templates'] | ||||
| @@ -38,7 +38,7 @@ master_doc = 'index' | ||||
|  | ||||
| # General information about the project. | ||||
| project = u'MongoEngine' | ||||
| copyright = u'2009, Harry Marr' | ||||
| copyright = u'2009-2010, Harry Marr' | ||||
|  | ||||
| # The version info for the project you're documenting, acts as replacement for | ||||
| # |version| and |release|, also used in various other places throughout the | ||||
|   | ||||
| @@ -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 | ||||
| :attr:`id` and returns a :class:`~mongoengine.django.auth.User` object. | ||||
|  | ||||
| .. versionadded:: 0.1.3 | ||||
|  | ||||
| Sessions | ||||
| ======== | ||||
| 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:: | ||||
|  | ||||
|     SESSION_ENGINE = 'mongoengine.django.sessions' | ||||
|  | ||||
| .. versionadded:: 0.2.1 | ||||
|   | ||||
							
								
								
									
										20
									
								
								docs/guide/connecting.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								docs/guide/connecting.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| .. _guide-connecting: | ||||
|  | ||||
| ===================== | ||||
| Connecting to MongoDB | ||||
| ===================== | ||||
| To connect to a running instance of :program:`mongod`, use the | ||||
| :func:`~mongoengine.connect` function. The first argument is the name of the | ||||
| database to connect to. If the database does not exist, it will be created. If | ||||
| the database requires authentication, :attr:`username` and :attr:`password` | ||||
| arguments may be provided:: | ||||
|  | ||||
|     from mongoengine import connect | ||||
|     connect('project1', username='webapp', password='pwd123') | ||||
|  | ||||
| By default, MongoEngine assumes that the :program:`mongod` instance is running | ||||
| on **localhost** on port **27017**. If MongoDB is running elsewhere, you may | ||||
| provide :attr:`host` and :attr:`port` arguments to | ||||
| :func:`~mongoengine.connect`:: | ||||
|  | ||||
|     connect('project1', host='192.168.1.35', port=12345) | ||||
							
								
								
									
										304
									
								
								docs/guide/defining-documents.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										304
									
								
								docs/guide/defining-documents.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,304 @@ | ||||
| ================== | ||||
| Defining documents | ||||
| ================== | ||||
| In MongoDB, a **document** is roughly equivalent to a **row** in an RDBMS. When | ||||
| working with relational databases, rows are stored in **tables**, which have a | ||||
| strict **schema** that the rows follow. MongoDB stores documents in | ||||
| **collections** rather than tables - the principle difference is that no schema  | ||||
| is enforced at a database level.  | ||||
|  | ||||
| Defining a document's schema | ||||
| ============================ | ||||
| MongoEngine allows you to define schemata for documents as this helps to reduce | ||||
| coding errors, and allows for utility methods to be defined on fields which may | ||||
| be present.  | ||||
|  | ||||
| To define a schema for a document, create a class that inherits from | ||||
| :class:`~mongoengine.Document`. Fields are specified by adding **field | ||||
| objects** as class attributes to the document class:: | ||||
|  | ||||
|     from mongoengine import * | ||||
|     import datetime | ||||
|      | ||||
|     class Page(Document): | ||||
|         title = StringField(max_length=200, required=True) | ||||
|         date_modified = DateTimeField(default=datetime.now) | ||||
|  | ||||
| Fields | ||||
| ====== | ||||
| By default, fields are not required. To make a field mandatory, set the | ||||
| :attr:`required` keyword argument of a field to ``True``. Fields also may have | ||||
| validation constraints available (such as :attr:`max_length` in the example | ||||
| above). Fields may also take default values, which will be used if a value is | ||||
| not provided. Default values may optionally be a callable, which will be called | ||||
| to retrieve the value (such as in the above example). The field types available  | ||||
| are as follows: | ||||
|  | ||||
| * :class:`~mongoengine.StringField` | ||||
| * :class:`~mongoengine.URLField` | ||||
| * :class:`~mongoengine.IntField` | ||||
| * :class:`~mongoengine.FloatField` | ||||
| * :class:`~mongoengine.DecimalField` | ||||
| * :class:`~mongoengine.DateTimeField` | ||||
| * :class:`~mongoengine.ListField` | ||||
| * :class:`~mongoengine.DictField` | ||||
| * :class:`~mongoengine.ObjectIdField` | ||||
| * :class:`~mongoengine.EmbeddedDocumentField` | ||||
| * :class:`~mongoengine.ReferenceField` | ||||
| * :class:`~mongoengine.GenericReferenceField` | ||||
|  | ||||
| List fields | ||||
| ----------- | ||||
| MongoDB allows the storage of lists of items. To add a list of items to a | ||||
| :class:`~mongoengine.Document`, use the :class:`~mongoengine.ListField` field | ||||
| type. :class:`~mongoengine.ListField` takes another field object as its first | ||||
| argument, which specifies which type elements may be stored within the list:: | ||||
|  | ||||
|     class Page(Document): | ||||
|         tags = ListField(StringField(max_length=50)) | ||||
|  | ||||
| Embedded documents | ||||
| ------------------ | ||||
| MongoDB has the ability to embed documents within other documents. Schemata may | ||||
| be defined for these embedded documents, just as they may be for regular | ||||
| documents. To create an embedded document, just define a document as usual, but | ||||
| inherit from :class:`~mongoengine.EmbeddedDocument` rather than  | ||||
| :class:`~mongoengine.Document`:: | ||||
|  | ||||
|     class Comment(EmbeddedDocument): | ||||
|         content = StringField() | ||||
|  | ||||
| To embed the document within another document, use the | ||||
| :class:`~mongoengine.EmbeddedDocumentField` field type, providing the embedded | ||||
| document class as the first argument:: | ||||
|  | ||||
|     class Page(Document): | ||||
|         comments = ListField(EmbeddedDocumentField(Comment)) | ||||
|  | ||||
|     comment1 = Comment('Good work!') | ||||
|     comment2 = Comment('Nice article!') | ||||
|     page = Page(comments=[comment1, comment2]) | ||||
|  | ||||
| Dictionary Fields | ||||
| ----------------- | ||||
| Often, an embedded document may be used instead of a dictionary -- generally | ||||
| this is recommended as dictionaries don't support validation or custom field | ||||
| types. However, sometimes you will not know the structure of what you want to | ||||
| store; in this situation a :class:`~mongoengine.DictField` is appropriate:: | ||||
|      | ||||
|     class SurveyResponse(Document): | ||||
|         date = DateTimeField() | ||||
|         user = ReferenceField(User) | ||||
|         answers = DictField() | ||||
|  | ||||
|     survey_response = SurveyResponse(date=datetime.now(), user=request.user) | ||||
|     response_form = ResponseForm(request.POST) | ||||
|     survey_response.answers = response_form.cleaned_data()    | ||||
|     survey_response.save() | ||||
|  | ||||
| Reference fields | ||||
| ---------------- | ||||
| References may be stored to other documents in the database using the | ||||
| :class:`~mongoengine.ReferenceField`. Pass in another document class as the | ||||
| first argument to the constructor, then simply assign document objects to the | ||||
| field:: | ||||
|      | ||||
|     class User(Document): | ||||
|         name = StringField() | ||||
|  | ||||
|     class Page(Document): | ||||
|         content = StringField() | ||||
|         author = ReferenceField(User) | ||||
|  | ||||
|     john = User(name="John Smith") | ||||
|     john.save() | ||||
|  | ||||
|     post = Page(content="Test Page") | ||||
|     post.author = john | ||||
|     post.save() | ||||
|  | ||||
| The :class:`User` object is automatically turned into a reference behind the | ||||
| scenes, and dereferenced when the :class:`Page` object is retrieved. | ||||
|  | ||||
| To add a :class:`~mongoengine.ReferenceField` that references the document | ||||
| being defined, use the string ``'self'`` in place of the document class as the | ||||
| argument to :class:`~mongoengine.ReferenceField`'s constructor. To reference a | ||||
| document that has not yet been defined, use the name of the undefined document | ||||
| as the constructor's argument:: | ||||
|  | ||||
|     class Employee(Document): | ||||
|         name = StringField() | ||||
|         boss = ReferenceField('self') | ||||
|         profile_page = ReferenceField('ProfilePage') | ||||
|  | ||||
|     class ProfilePage(Document): | ||||
|         content = StringField() | ||||
|  | ||||
| Generic reference fields | ||||
| '''''''''''''''''''''''' | ||||
| A second kind of reference field also exists, | ||||
| :class:`~mongoengine.GenericReferenceField`. This allows you to reference any | ||||
| kind of :class:`~mongoengine.Document`, and hence doesn't take a  | ||||
| :class:`~mongoengine.Document` subclass as a constructor argument:: | ||||
|  | ||||
|     class Link(Document): | ||||
|         url = StringField() | ||||
|          | ||||
|     class Post(Document): | ||||
|         title = StringField() | ||||
|          | ||||
|     class Bookmark(Document): | ||||
|         bookmark_object = GenericReferenceField() | ||||
|  | ||||
|     link = Link(url='http://hmarr.com/mongoengine/') | ||||
|     link.save() | ||||
|  | ||||
|     post = Post(title='Using MongoEngine') | ||||
|     post.save() | ||||
|  | ||||
|     Bookmark(bookmark_object=link).save() | ||||
|     Bookmark(bookmark_object=post).save() | ||||
|  | ||||
| .. note:: | ||||
|    Using :class:`~mongoengine.GenericReferenceField`\ s is slightly less | ||||
|    efficient than the standard :class:`~mongoengine.ReferenceField`\ s, so if | ||||
|    you will only be referencing one document type, prefer the standard  | ||||
|    :class:`~mongoengine.ReferenceField`. | ||||
|  | ||||
| Uniqueness constraints | ||||
| ---------------------- | ||||
| MongoEngine allows you to specify that a field should be unique across a | ||||
| collection by providing ``unique=True`` to a :class:`~mongoengine.Field`\ 's | ||||
| constructor. If you try to save a document that has the same value for a unique | ||||
| field as a document that is already in the database, a  | ||||
| :class:`~mongoengine.OperationError` will be raised. You may also specify | ||||
| multi-field uniqueness constraints by using :attr:`unique_with`, which may be | ||||
| either a single field name, or a list or tuple of field names:: | ||||
|  | ||||
|     class User(Document): | ||||
|         username = StringField(unique=True) | ||||
|         first_name = StringField() | ||||
|         last_name = StringField(unique_with='last_name') | ||||
|  | ||||
| Document collections | ||||
| ==================== | ||||
| Document classes that inherit **directly** from :class:`~mongoengine.Document` | ||||
| will have their own **collection** in the database. The name of the collection | ||||
| is by default the name of the class, coverted to lowercase (so in the example | ||||
| above, the collection would be called `page`). If you need to change the name | ||||
| of the collection (e.g. to use MongoEngine with an existing database), then | ||||
| create a class dictionary attribute called :attr:`meta` on your document, and | ||||
| set :attr:`collection` to the name of the collection that you want your | ||||
| document class to use:: | ||||
|  | ||||
|     class Page(Document): | ||||
|         title = StringField(max_length=200, required=True) | ||||
|         meta = {'collection': 'cmsPage'} | ||||
|  | ||||
| Capped collections | ||||
| ------------------ | ||||
| A :class:`~mongoengine.Document` may use a **Capped Collection** by specifying | ||||
| :attr:`max_documents` and :attr:`max_size` in the :attr:`meta` dictionary. | ||||
| :attr:`max_documents` is the maximum number of documents that is allowed to be | ||||
| stored in the collection, and :attr:`max_size` is the maximum size of the | ||||
| collection in bytes. If :attr:`max_size` is not specified and | ||||
| :attr:`max_documents` is, :attr:`max_size` defaults to 10000000 bytes (10MB). | ||||
| The following example shows a :class:`Log` document that will be limited to  | ||||
| 1000 entries and 2MB of disk space:: | ||||
|  | ||||
|     class Log(Document): | ||||
|         ip_address = StringField() | ||||
|         meta = {'max_documents': 1000, 'max_size': 2000000} | ||||
|  | ||||
| Indexes | ||||
| ======= | ||||
| You can specify indexes on collections to make querying faster. This is done | ||||
| by creating a list of index specifications called :attr:`indexes` in the | ||||
| :attr:`~mongoengine.Document.meta` dictionary, where an index specification may | ||||
| either be a single field name, or a tuple containing multiple field names. A | ||||
| direction may be specified on fields by prefixing the field name with a **+** | ||||
| or a **-** sign. Note that direction only matters on multi-field indexes. :: | ||||
|  | ||||
|     class Page(Document): | ||||
|         title = StringField() | ||||
|         rating = StringField() | ||||
|         meta = { | ||||
|             'indexes': ['title', ('title', '-rating')] | ||||
|         } | ||||
|          | ||||
| Ordering | ||||
| ======== | ||||
| A default ordering can be specified for your | ||||
| :class:`~mongoengine.queryset.QuerySet` using the :attr:`ordering` attribute of | ||||
| :attr:`~mongoengine.Document.meta`.  Ordering will be applied when the | ||||
| :class:`~mongoengine.queryset.QuerySet` is created, and can be overridden by | ||||
| subsequent calls to :meth:`~mongoengine.queryset.QuerySet.order_by`. :: | ||||
|  | ||||
|     from datetime import datetime | ||||
|  | ||||
|     class BlogPost(Document): | ||||
|         title = StringField() | ||||
|         published_date = DateTimeField() | ||||
|  | ||||
|         meta = { | ||||
|             'ordering': ['-published_date'] | ||||
|         } | ||||
|  | ||||
|     blog_post_1 = BlogPost(title="Blog Post #1") | ||||
|     blog_post_1.published_date = datetime(2010, 1, 5, 0, 0 ,0) | ||||
|  | ||||
|     blog_post_2 = BlogPost(title="Blog Post #2")  | ||||
|     blog_post_2.published_date = datetime(2010, 1, 6, 0, 0 ,0) | ||||
|  | ||||
|     blog_post_3 = BlogPost(title="Blog Post #3") | ||||
|     blog_post_3.published_date = datetime(2010, 1, 7, 0, 0 ,0) | ||||
|  | ||||
|     blog_post_1.save() | ||||
|     blog_post_2.save() | ||||
|     blog_post_3.save() | ||||
|  | ||||
|     # get the "first" BlogPost using default ordering | ||||
|     # from BlogPost.meta.ordering | ||||
|     latest_post = BlogPost.objects.first()  | ||||
|     assert latest_post.title == "Blog Post #3" | ||||
|  | ||||
|     # override default ordering, order BlogPosts by "published_date" | ||||
|     first_post = BlogPost.objects.order_by("+published_date").first() | ||||
|     assert first_post.title == "Blog Post #1" | ||||
|  | ||||
| Document inheritance | ||||
| ==================== | ||||
| To create a specialised type of a :class:`~mongoengine.Document` you have | ||||
| defined, you may subclass it and add any extra fields or methods you may need. | ||||
| As this is new class is not a direct subclass of | ||||
| :class:`~mongoengine.Document`, it will not be stored in its own collection; it | ||||
| will use the same collection as its superclass uses. This allows for more | ||||
| convenient and efficient retrieval of related documents:: | ||||
|  | ||||
|     # Stored in a collection named 'page' | ||||
|     class Page(Document): | ||||
|         title = StringField(max_length=200, required=True) | ||||
|  | ||||
|     # Also stored in the collection named 'page' | ||||
|     class DatedPage(Page): | ||||
|         date = DateTimeField() | ||||
|  | ||||
| Working with existing data | ||||
| -------------------------- | ||||
| To enable correct retrieval of documents involved in this kind of heirarchy, | ||||
| two extra attributes are stored on each document in the database: :attr:`_cls` | ||||
| and :attr:`_types`. These are hidden from the user through the MongoEngine | ||||
| interface, but may not be present if you are trying to use MongoEngine with  | ||||
| an existing database. For this reason, you may disable this inheritance | ||||
| mechansim, removing the dependency of :attr:`_cls` and :attr:`_types`, enabling | ||||
| you to work with existing databases. To disable inheritance on a document | ||||
| class, set :attr:`allow_inheritance` to ``False`` in the :attr:`meta` | ||||
| dictionary:: | ||||
|  | ||||
|     # Will work with data in an existing collection named 'cmsPage' | ||||
|     class Page(Document): | ||||
|         title = StringField(max_length=200, required=True) | ||||
|         meta = { | ||||
|             'collection': 'cmsPage', | ||||
|             'allow_inheritance': False, | ||||
|         } | ||||
							
								
								
									
										65
									
								
								docs/guide/document-instances.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								docs/guide/document-instances.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| =================== | ||||
| Documents instances | ||||
| =================== | ||||
| To create a new document object, create an instance of the relevant document | ||||
| class, providing values for its fields as its constructor keyword arguments. | ||||
| You may provide values for any of the fields on the document:: | ||||
|      | ||||
|     >>> page = Page(title="Test Page") | ||||
|     >>> page.title | ||||
|     'Test Page' | ||||
|  | ||||
| You may also assign values to the document's fields using standard object  | ||||
| attribute syntax:: | ||||
|  | ||||
|     >>> page.title = "Example Page" | ||||
|     >>> page.title | ||||
|     'Example Page' | ||||
|  | ||||
| Saving and deleting documents | ||||
| ============================= | ||||
| To save the document to the database, call the | ||||
| :meth:`~mongoengine.Document.save` method. If the document does not exist in | ||||
| the database, it will be created. If it does already exist, it will be | ||||
| updated. | ||||
|  | ||||
| To delete a document, call the :meth:`~mongoengine.Document.delete` method. | ||||
| Note that this will only work if the document exists in the database and has a | ||||
| valide :attr:`id`. | ||||
|  | ||||
| .. seealso:: | ||||
|     :ref:`guide-atomic-updates` | ||||
|  | ||||
| Document IDs | ||||
| ============ | ||||
| Each document in the database has a unique id. This may be accessed through the | ||||
| :attr:`id` attribute on :class:`~mongoengine.Document` objects. Usually, the id | ||||
| will be generated automatically by the database server when the object is save, | ||||
| meaning that you may only access the :attr:`id` field once a document has been | ||||
| saved:: | ||||
|  | ||||
|     >>> page = Page(title="Test Page") | ||||
|     >>> page.id | ||||
|     >>> page.save() | ||||
|     >>> page.id | ||||
|     ObjectId('123456789abcdef000000000') | ||||
|  | ||||
| Alternatively, you may define one of your own fields to be the document's | ||||
| "primary key" by providing ``primary_key=True`` as a keyword argument to a | ||||
| field's constructor. Under the hood, MongoEngine will use this field as the | ||||
| :attr:`id`; in fact :attr:`id` is actually aliased to your primary key field so | ||||
| you may still use :attr:`id` to access the primary key if you want:: | ||||
|  | ||||
|     >>> class User(Document): | ||||
|     ...     email = StringField(primary_key=True) | ||||
|     ...     name = StringField() | ||||
|     ... | ||||
|     >>> bob = User(email='bob@example.com', name='Bob') | ||||
|     >>> bob.save() | ||||
|     >>> bob.id == bob.email == 'bob@example.com' | ||||
|     True | ||||
|  | ||||
| .. note:: | ||||
|    If you define your own primary key field, the field implicitly becomes | ||||
|    required, so a :class:`ValidationError` will be thrown if you don't provide | ||||
|    it. | ||||
							
								
								
									
										12
									
								
								docs/guide/index.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								docs/guide/index.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| ========== | ||||
| User Guide | ||||
| ========== | ||||
|  | ||||
| .. toctree:: | ||||
|    :maxdepth: 2 | ||||
|  | ||||
|    installing | ||||
|    connecting | ||||
|    defining-documents | ||||
|    document-instances | ||||
|    querying | ||||
							
								
								
									
										31
									
								
								docs/guide/installing.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								docs/guide/installing.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| ====================== | ||||
| Installing MongoEngine | ||||
| ====================== | ||||
| To use MongoEngine, you will need to download `MongoDB <http://mongodb.org/>`_ | ||||
| and ensure it is running in an accessible location. You will also need | ||||
| `PyMongo <http://api.mongodb.org/python>`_ to use MongoEngine, but if you | ||||
| install MongoEngine using setuptools, then the dependencies will be handled for | ||||
| you. | ||||
|  | ||||
| MongoEngine is available on PyPI, so to use it you can use  | ||||
| :program:`easy_install`: | ||||
|      | ||||
| .. code-block:: console | ||||
|  | ||||
|     # easy_install mongoengine | ||||
|  | ||||
| Alternatively, if you don't have setuptools installed, `download it from PyPi  | ||||
| <http://pypi.python.org/pypi/mongoengine/>`_ and run | ||||
|  | ||||
| .. code-block:: console | ||||
|  | ||||
|     # python setup.py install | ||||
|  | ||||
| To use the bleeding-edge version of MongoEngine, you can get the source from | ||||
| `GitHub <http://github.com/hmarr/mongoengine/>`_ and install it as above: | ||||
|      | ||||
| .. code-block:: console | ||||
|  | ||||
|     # git clone git://github.com/hmarr/mongoengine | ||||
|     # cd mongoengine | ||||
|     # python setup.py install | ||||
							
								
								
									
										421
									
								
								docs/guide/querying.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										421
									
								
								docs/guide/querying.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,421 @@ | ||||
| ===================== | ||||
| Querying the database | ||||
| ===================== | ||||
| :class:`~mongoengine.Document` classes have an :attr:`objects` attribute, which | ||||
| is used for accessing the objects in the database associated with the class. | ||||
| The :attr:`objects` attribute is actually a | ||||
| :class:`~mongoengine.queryset.QuerySetManager`, which creates and returns a new | ||||
| a new :class:`~mongoengine.queryset.QuerySet` object on access. The | ||||
| :class:`~mongoengine.queryset.QuerySet` object may may be iterated over to | ||||
| fetch documents from the database:: | ||||
|  | ||||
|     # Prints out the names of all the users in the database | ||||
|     for user in User.objects: | ||||
|         print user.name | ||||
|  | ||||
| .. note:: | ||||
|    Once the iteration finishes (when :class:`StopIteration` is raised), | ||||
|    :meth:`~mongoengine.queryset.QuerySet.rewind` will be called so that the | ||||
|    :class:`~mongoengine.queryset.QuerySet` may be iterated over again. The | ||||
|    results of the first iteration are *not* cached, so the database will be hit | ||||
|    each time the :class:`~mongoengine.queryset.QuerySet` is iterated over. | ||||
|  | ||||
| Filtering queries | ||||
| ================= | ||||
| The query may be filtered by calling the | ||||
| :class:`~mongoengine.queryset.QuerySet` object with field lookup keyword  | ||||
| arguments. The keys in the keyword arguments correspond to fields on the | ||||
| :class:`~mongoengine.Document` you are querying:: | ||||
|  | ||||
|     # This will return a QuerySet that will only iterate over users whose | ||||
|     # 'country' field is set to 'uk' | ||||
|     uk_users = User.objects(country='uk') | ||||
|  | ||||
| Fields on embedded documents may also be referred to using field lookup syntax | ||||
| by using a double-underscore in place of the dot in object attribute access | ||||
| syntax:: | ||||
|      | ||||
|     # This will return a QuerySet that will only iterate over pages that have | ||||
|     # been written by a user whose 'country' field is set to 'uk' | ||||
|     uk_pages = Page.objects(author__country='uk') | ||||
|  | ||||
| Querying lists | ||||
| -------------- | ||||
| On most fields, this syntax will look up documents where the field specified | ||||
| matches the given value exactly, but when the field refers to a | ||||
| :class:`~mongoengine.ListField`, a single item may be provided, in which case | ||||
| lists that contain that item will be matched:: | ||||
|  | ||||
|     class Page(Document): | ||||
|         tags = ListField(StringField()) | ||||
|  | ||||
|     # This will match all pages that have the word 'coding' as an item in the | ||||
|     # 'tags' list | ||||
|     Page.objects(tags='coding') | ||||
|  | ||||
| Query operators | ||||
| =============== | ||||
| Operators other than equality may also be used in queries; just attach the | ||||
| operator name to a key with a double-underscore:: | ||||
|      | ||||
|     # Only find users whose age is 18 or less | ||||
|     young_users = Users.objects(age__lte=18) | ||||
|  | ||||
| Available operators are as follows: | ||||
|  | ||||
| * ``neq`` -- not equal to | ||||
| * ``lt`` -- less than | ||||
| * ``lte`` -- less than or equal to | ||||
| * ``gt`` -- greater than | ||||
| * ``gte`` -- greater than or equal to | ||||
| * ``in`` -- value is in list (a list of values should be provided) | ||||
| * ``nin`` -- value is not in list (a list of values should be provided) | ||||
| * ``mod`` -- ``value % x == y``, where ``x`` and ``y`` are two provided values | ||||
| * ``all`` -- every item in array is in list of values provided | ||||
| * ``size`` -- the size of the array is  | ||||
| * ``exists`` -- value for field exists | ||||
|  | ||||
| The following operators are available as shortcuts to querying with regular | ||||
| expressions: | ||||
|  | ||||
| * ``contains`` -- string field contains value | ||||
| * ``icontains`` -- string field contains value (case insensitive) | ||||
| * ``startswith`` -- string field starts with value | ||||
| * ``istartswith`` -- string field starts with value (case insensitive) | ||||
| * ``endswith`` -- string field ends with value | ||||
| * ``iendswith`` -- string field ends with value (case insensitive) | ||||
|  | ||||
| .. versionadded:: 0.3 | ||||
|  | ||||
| Limiting and skipping results | ||||
| ============================= | ||||
| Just as with traditional ORMs, you may limit the number of results returned, or | ||||
| skip a number or results in you query. | ||||
| :meth:`~mongoengine.queryset.QuerySet.limit` and | ||||
| :meth:`~mongoengine.queryset.QuerySet.skip` and methods are available on | ||||
| :class:`~mongoengine.queryset.QuerySet` objects, but the prefered syntax for | ||||
| achieving this is using array-slicing syntax:: | ||||
|  | ||||
|     # Only the first 5 people | ||||
|     users = User.objects[:5] | ||||
|  | ||||
|     # All except for the first 5 people | ||||
|     users = User.objects[5:] | ||||
|  | ||||
|     # 5 users, starting from the 10th user found | ||||
|     users = User.objects[10:15] | ||||
|  | ||||
| You may also index the query to retrieve a single result. If an item at that | ||||
| index does not exists, an :class:`IndexError` will be raised. A shortcut for | ||||
| retrieving the first result and returning :attr:`None` if no result exists is | ||||
| provided (:meth:`~mongoengine.queryset.QuerySet.first`):: | ||||
|      | ||||
|     >>> # Make sure there are no users | ||||
|     >>> User.drop_collection() | ||||
|     >>> User.objects[0] | ||||
|     IndexError: list index out of range | ||||
|     >>> User.objects.first() == None | ||||
|     True | ||||
|     >>> User(name='Test User').save() | ||||
|     >>> User.objects[0] == User.objects.first() | ||||
|     True | ||||
|  | ||||
| Retrieving unique results | ||||
| ------------------------- | ||||
| To retrieve a result that should be unique in the collection, use | ||||
| :meth:`~mongoengine.queryset.QuerySet.get`. This will raise | ||||
| :class:`~mongoengine.queryset.DoesNotExist` if no document matches the query, | ||||
| and :class:`~mongoengine.queryset.MultipleObjectsReturned` if more than one | ||||
| document matched the query. | ||||
|  | ||||
| A variation of this method exists,  | ||||
| :meth:`~mongoengine.queryset.Queryset.get_or_create`, that will create a new | ||||
| document with the query arguments if no documents match the query. An  | ||||
| additional keyword argument, :attr:`defaults` may be provided, which will be | ||||
| used as default values for the new document, in the case that it should need | ||||
| to be created:: | ||||
|  | ||||
|     >>> a = User.objects.get_or_create(name='User A', defaults={'age': 30}) | ||||
|     >>> b = User.objects.get_or_create(name='User A', defaults={'age': 40}) | ||||
|     >>> a.name == b.name and a.age == b.age | ||||
|     True | ||||
|  | ||||
| Default Document queries | ||||
| ======================== | ||||
| By default, the objects :attr:`~mongoengine.Document.objects` attribute on a | ||||
| document returns a :class:`~mongoengine.queryset.QuerySet` that doesn't filter | ||||
| the collection -- it returns all objects. This may be changed by defining a | ||||
| method on a document that modifies a queryset. The method should accept two | ||||
| arguments -- :attr:`doc_cls` and :attr:`queryset`. The first argument is the | ||||
| :class:`~mongoengine.Document` class that the method is defined on (in this | ||||
| sense, the method is more like a :func:`classmethod` than a regular method), | ||||
| and the second argument is the initial queryset. The method needs to be | ||||
| decorated with :func:`~mongoengine.queryset.queryset_manager` in order for it | ||||
| to be recognised. :: | ||||
|  | ||||
|     class BlogPost(Document): | ||||
|         title = StringField() | ||||
|         date = DateTimeField() | ||||
|  | ||||
|         @queryset_manager | ||||
|         def objects(doc_cls, queryset): | ||||
|             # This may actually also be done by defining a default ordering for | ||||
|             # the document, but this illustrates the use of manager methods | ||||
|             return queryset.order_by('-date') | ||||
|  | ||||
| You don't need to call your method :attr:`objects` -- you may define as many | ||||
| custom manager methods as you like:: | ||||
|  | ||||
|     class BlogPost(Document): | ||||
|         title = StringField() | ||||
|         published = BooleanField() | ||||
|  | ||||
|         @queryset_manager | ||||
|         def live_posts(doc_cls, queryset): | ||||
|             return queryset.order_by('-date') | ||||
|  | ||||
|     BlogPost(title='test1', published=False).save() | ||||
|     BlogPost(title='test2', published=True).save() | ||||
|     assert len(BlogPost.objects) == 2 | ||||
|     assert len(BlogPost.live_posts) == 1 | ||||
|  | ||||
| Aggregation | ||||
| =========== | ||||
| MongoDB provides some aggregation methods out of the box, but there are not as | ||||
| many as you typically get with an RDBMS. MongoEngine provides a wrapper around | ||||
| the built-in methods and provides some of its own, which are implemented as | ||||
| Javascript code that is executed on the database server. | ||||
|  | ||||
| Counting results | ||||
| ---------------- | ||||
| Just as with limiting and skipping results, there is a method on | ||||
| :class:`~mongoengine.queryset.QuerySet` objects --  | ||||
| :meth:`~mongoengine.queryset.QuerySet.count`, but there is also a more Pythonic | ||||
| way of achieving this:: | ||||
|  | ||||
|     num_users = len(User.objects) | ||||
|  | ||||
| Further aggregation | ||||
| ------------------- | ||||
| You may sum over the values of a specific field on documents using | ||||
| :meth:`~mongoengine.queryset.QuerySet.sum`:: | ||||
|  | ||||
|     yearly_expense = Employee.objects.sum('salary') | ||||
|  | ||||
| .. note:: | ||||
|    If the field isn't present on a document, that document will be ignored from | ||||
|    the sum. | ||||
|  | ||||
| To get the average (mean) of a field on a collection of documents, use | ||||
| :meth:`~mongoengine.queryset.QuerySet.average`:: | ||||
|  | ||||
|     mean_age = User.objects.average('age') | ||||
|  | ||||
| As MongoDB provides native lists, MongoEngine provides a helper method to get a | ||||
| dictionary of the frequencies of items in lists across an entire collection -- | ||||
| :meth:`~mongoengine.queryset.QuerySet.item_frequencies`. An example of its use | ||||
| would be generating "tag-clouds":: | ||||
|  | ||||
|     class Article(Document): | ||||
|         tag = ListField(StringField()) | ||||
|  | ||||
|     # After adding some tagged articles... | ||||
|     tag_freqs = Article.objects.item_frequencies('tag', normalize=True) | ||||
|  | ||||
|     from operator import itemgetter | ||||
|     top_tags = sorted(tag_freqs.items(), key=itemgetter(1), reverse=True)[:10] | ||||
|  | ||||
| Retrieving a subset of fields | ||||
| ============================= | ||||
| Sometimes a subset of fields on a :class:`~mongoengine.Document` is required, | ||||
| and for efficiency only these should be retrieved from the database. This issue | ||||
| is especially important for MongoDB, as fields may often be extremely large | ||||
| (e.g. a :class:`~mongoengine.ListField` of | ||||
| :class:`~mongoengine.EmbeddedDocument`\ s, which represent the comments on a | ||||
| blog post. To select only a subset of fields, use | ||||
| :meth:`~mongoengine.queryset.QuerySet.only`, specifying the fields you want to | ||||
| retrieve as its arguments. Note that if fields that are not downloaded are | ||||
| accessed, their default value (or :attr:`None` if no default value is provided) | ||||
| will be given:: | ||||
|  | ||||
|     >>> class Film(Document): | ||||
|     ...     title = StringField() | ||||
|     ...     year = IntField() | ||||
|     ...     rating = IntField(default=3) | ||||
|     ... | ||||
|     >>> Film(title='The Shawshank Redemption', year=1994, rating=5).save() | ||||
|     >>> f = Film.objects.only('title').first() | ||||
|     >>> f.title | ||||
|     'The Shawshank Redemption' | ||||
|     >>> f.year   # None | ||||
|     >>> f.rating # default value | ||||
|     3 | ||||
|  | ||||
| If you later need the missing fields, just call | ||||
| :meth:`~mongoengine.Document.reload` on your document. | ||||
|  | ||||
| Advanced queries | ||||
| ================ | ||||
| Sometimes calling a :class:`~mongoengine.queryset.QuerySet` object with keyword | ||||
| arguments can't fully express the query you want to use -- for example if you | ||||
| need to combine a number of constraints using *and* and *or*. This is made  | ||||
| possible in MongoEngine through the :class:`~mongoengine.queryset.Q` class. | ||||
| A :class:`~mongoengine.queryset.Q` object represents part of a query, and | ||||
| can be initialised using the same keyword-argument syntax you use to query | ||||
| documents. To build a complex query, you may combine  | ||||
| :class:`~mongoengine.queryset.Q` objects using the ``&`` (and) and ``|`` (or) | ||||
| operators. To use a :class:`~mongoengine.queryset.Q` object, pass it in as the | ||||
| first positional argument to :attr:`Document.objects` when you filter it by | ||||
| calling it with keyword arguments:: | ||||
|  | ||||
|     # Get published posts | ||||
|     Post.objects(Q(published=True) | Q(publish_date__lte=datetime.now())) | ||||
|  | ||||
|     # Get top posts | ||||
|     Post.objects((Q(featured=True) & Q(hits__gte=1000)) | Q(hits__gte=5000)) | ||||
|  | ||||
| .. warning:: | ||||
|    Only use these advanced queries if absolutely necessary as they will execute | ||||
|    significantly slower than regular queries. This is because they are not | ||||
|    natively supported by MongoDB -- they are compiled to Javascript and sent | ||||
|    to the server for execution. | ||||
|  | ||||
| Server-side javascript execution | ||||
| ================================ | ||||
| Javascript functions may be written and sent to the server for execution. The | ||||
| result of this is the return value of the Javascript function. This | ||||
| functionality is accessed through the | ||||
| :meth:`~mongoengine.queryset.QuerySet.exec_js` method on | ||||
| :meth:`~mongoengine.queryset.QuerySet` objects. Pass in a string containing a | ||||
| Javascript function as the first argument. | ||||
|  | ||||
| The remaining positional arguments are names of fields that will be passed into | ||||
| you Javascript function as its arguments. This allows functions to be written | ||||
| that may be executed on any field in a collection (e.g. the | ||||
| :meth:`~mongoengine.queryset.QuerySet.sum` method, which accepts the name of | ||||
| the field to sum over as its argument). Note that field names passed in in this | ||||
| manner are automatically translated to the names used on the database (set | ||||
| using the :attr:`name` keyword argument to a field constructor). | ||||
|  | ||||
| Keyword arguments to :meth:`~mongoengine.queryset.QuerySet.exec_js` are | ||||
| combined into an object called :attr:`options`, which is available in the | ||||
| Javascript function. This may be used for defining specific parameters for your | ||||
| function. | ||||
|  | ||||
| Some variables are made available in the scope of the Javascript function: | ||||
|  | ||||
| * ``collection`` -- the name of the collection that corresponds to the | ||||
|   :class:`~mongoengine.Document` class that is being used; this should be | ||||
|   used to get the :class:`Collection` object from :attr:`db` in Javascript | ||||
|   code | ||||
| * ``query`` -- the query that has been generated by the | ||||
|   :class:`~mongoengine.queryset.QuerySet` object; this may be passed into | ||||
|   the :meth:`find` method on a :class:`Collection` object in the Javascript | ||||
|   function | ||||
| * ``options`` -- an object containing the keyword arguments passed into | ||||
|   :meth:`~mongoengine.queryset.QuerySet.exec_js` | ||||
|  | ||||
| The following example demonstrates the intended usage of | ||||
| :meth:`~mongoengine.queryset.QuerySet.exec_js` by defining a function that sums | ||||
| over a field on a document (this functionality is already available throught | ||||
| :meth:`~mongoengine.queryset.QuerySet.sum` but is shown here for sake of | ||||
| example):: | ||||
|  | ||||
|     def sum_field(document, field_name, include_negatives=True): | ||||
|         code = """ | ||||
|         function(sumField) { | ||||
|             var total = 0.0; | ||||
|             db[collection].find(query).forEach(function(doc) { | ||||
|                 var val = doc[sumField]; | ||||
|                 if (val >= 0.0 || options.includeNegatives) { | ||||
|                     total += val; | ||||
|                 } | ||||
|             }); | ||||
|             return total; | ||||
|         } | ||||
|         """ | ||||
|         options = {'includeNegatives': include_negatives} | ||||
|         return document.objects.exec_js(code, field_name, **options) | ||||
|  | ||||
| As fields in MongoEngine may use different names in the database (set using the | ||||
| :attr:`db_field` keyword argument to a :class:`Field` constructor), a mechanism | ||||
| exists for replacing MongoEngine field names with the database field names in | ||||
| Javascript code. When accessing a field on a collection object, use | ||||
| square-bracket notation, and prefix the MongoEngine field name with a tilde. | ||||
| The field name that follows the tilde will be translated to the name used in | ||||
| the database. Note that when referring to fields on embedded documents, | ||||
| the name of the :class:`~mongoengine.EmbeddedDocumentField`, followed by a dot, | ||||
| should be used before the name of the field on the embedded document. The | ||||
| following example shows how the substitutions are made:: | ||||
|  | ||||
|     class Comment(EmbeddedDocument): | ||||
|         content = StringField(db_field='body') | ||||
|  | ||||
|     class BlogPost(Document): | ||||
|         title = StringField(db_field='doctitle') | ||||
|         comments = ListField(EmbeddedDocumentField(Comment), name='cs') | ||||
|  | ||||
|     # Returns a list of dictionaries. Each dictionary contains a value named | ||||
|     # "document", which corresponds to the "title" field on a BlogPost, and | ||||
|     # "comment", which corresponds to an individual comment. The substitutions | ||||
|     # made are shown in the comments. | ||||
|     BlogPost.objects.exec_js(""" | ||||
|     function() { | ||||
|         var comments = []; | ||||
|         db[collection].find(query).forEach(function(doc) { | ||||
|             // doc[~comments] -> doc["cs"] | ||||
|             var docComments = doc[~comments]; | ||||
|  | ||||
|             for (var i = 0; i < docComments.length; i++) { | ||||
|                 // doc[~comments][i] -> doc["cs"][i] | ||||
|                 var comment = doc[~comments][i]; | ||||
|  | ||||
|                 comments.push({ | ||||
|                     // doc[~title] -> doc["doctitle"] | ||||
|                     'document': doc[~title], | ||||
|  | ||||
|                     // comment[~comments.content] -> comment["body"] | ||||
|                     'comment': comment[~comments.content] | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
|         return comments; | ||||
|     } | ||||
|     """) | ||||
|  | ||||
| .. _guide-atomic-updates: | ||||
|  | ||||
| Atomic updates | ||||
| ============== | ||||
| Documents may be updated atomically by using the | ||||
| :meth:`~mongoengine.queryset.QuerySet.update_one` and | ||||
| :meth:`~mongoengine.queryset.QuerySet.update` methods on a  | ||||
| :meth:`~mongoengine.queryset.QuerySet`. There are several different "modifiers" | ||||
| that you may use with these methods: | ||||
|  | ||||
| * ``set`` -- set a particular value | ||||
| * ``unset`` -- delete a particular value (since MongoDB v1.3+) | ||||
| * ``inc`` -- increment a value by a given amount | ||||
| * ``dec`` -- decrement a value by a given amount | ||||
| * ``push`` -- append a value to a list | ||||
| * ``push_all`` -- append several values to a list | ||||
| * ``pull`` -- remove a value from a list | ||||
| * ``pull_all`` -- remove several values from a list | ||||
|  | ||||
| The syntax for atomic updates is similar to the querying syntax, but the  | ||||
| modifier comes before the field, not after it:: | ||||
|      | ||||
|     >>> post = BlogPost(title='Test', page_views=0, tags=['database']) | ||||
|     >>> post.save() | ||||
|     >>> BlogPost.objects(id=post.id).update_one(inc__page_views=1) | ||||
|     >>> post.reload()  # the document has been changed, so we need to reload it | ||||
|     >>> post.page_views | ||||
|     1 | ||||
|     >>> BlogPost.objects(id=post.id).update_one(set__title='Example Post') | ||||
|     >>> post.reload() | ||||
|     >>> post.title | ||||
|     'Example Post' | ||||
|     >>> BlogPost.objects(id=post.id).update_one(push__tags='nosql') | ||||
|     >>> post.reload() | ||||
|     >>> post.tags | ||||
|     ['database', 'nosql'] | ||||
| @@ -1,20 +1,28 @@ | ||||
| ============================== | ||||
| MongoEngine User Documentation | ||||
| ======================================= | ||||
| ============================== | ||||
|  | ||||
| MongoEngine is an Object-Document Mapper, written in Python for working with  | ||||
| MongoDB. To install it, simply run | ||||
|  | ||||
| .. code-block:: console | ||||
|  | ||||
|     # easy_install mongoengine | ||||
|     # easy_install -U mongoengine | ||||
|  | ||||
| The source is available on `GitHub <http://github.com/hmarr/mongoengine>`_. | ||||
|  | ||||
| To get help with using MongoEngine, use the `MongoEngine Users mailing list | ||||
| <http://groups.google.com/group/mongoengine-users>`_ or come chat on the | ||||
| `#mongoengine IRC channel <irc://irc.freenode.net/mongoengine>`_. | ||||
|  | ||||
| If you are interested in contributing, join the developers' `mailing list  | ||||
| <http://groups.google.com/group/mongoengine-dev>`_. | ||||
|  | ||||
| .. toctree:: | ||||
|    :maxdepth: 2 | ||||
|  | ||||
|    tutorial | ||||
|    userguide | ||||
|    guide/index | ||||
|    apireference | ||||
|    django | ||||
|    changelog | ||||
|   | ||||
| @@ -1,534 +0,0 @@ | ||||
| ========== | ||||
| User Guide | ||||
| ========== | ||||
|  | ||||
| Installing | ||||
| ========== | ||||
| MongoEngine is available on PyPI, so to use it you can use  | ||||
| :program:`easy_install` | ||||
|      | ||||
| .. code-block:: console | ||||
|  | ||||
|     # easy_install mongoengine | ||||
|  | ||||
| Alternatively, if you don't have setuptools installed, `download it from PyPi  | ||||
| <http://pypi.python.org/pypi/mongoengine/>`_ and run | ||||
|  | ||||
| .. code-block:: console | ||||
|  | ||||
|     # python setup.py install | ||||
|  | ||||
| .. _guide-connecting: | ||||
|  | ||||
| Connecting to MongoDB | ||||
| ===================== | ||||
| To connect to a running instance of :program:`mongod`, use the | ||||
| :func:`~mongoengine.connect` function. The first argument is the name of the | ||||
| database to connect to. If the database does not exist, it will be created. If | ||||
| the database requires authentication, :attr:`username` and :attr:`password` | ||||
| arguments may be provided:: | ||||
|  | ||||
|     from mongoengine import connect | ||||
|     connect('project1', username='webapp', password='pwd123') | ||||
|  | ||||
| By default, MongoEngine assumes that the :program:`mongod` instance is running | ||||
| on **localhost** on port **27017**. If MongoDB is running elsewhere, you may | ||||
| provide :attr:`host` and :attr:`port` arguments to | ||||
| :func:`~mongoengine.connect`:: | ||||
|  | ||||
|     connect('project1', host='192.168.1.35', port=12345) | ||||
|  | ||||
| Defining documents | ||||
| ================== | ||||
| In MongoDB, a **document** is roughly equivalent to a **row** in an RDBMS. When | ||||
| working with relational databases, rows are stored in **tables**, which have a | ||||
| strict **schema** that the rows follow. MongoDB stores documents in | ||||
| **collections** rather than tables - the principle difference is that no schema  | ||||
| is enforced at a database level.  | ||||
|  | ||||
| Defining a document's schema | ||||
| ---------------------------- | ||||
| MongoEngine allows you to define schemata for documents as this helps to reduce | ||||
| coding errors, and allows for utility methods to be defined on fields which may | ||||
| be present.  | ||||
|  | ||||
| To define a schema for a document, create a class that inherits from | ||||
| :class:`~mongoengine.Document`. Fields are specified by adding **field | ||||
| objects** as class attributes to the document class:: | ||||
|  | ||||
|     from mongoengine import * | ||||
|     import datetime | ||||
|      | ||||
|     class Page(Document): | ||||
|         title = StringField(max_length=200, required=True) | ||||
|         date_modified = DateTimeField(default=datetime.now) | ||||
|  | ||||
| Fields | ||||
| ------ | ||||
| By default, fields are not required. To make a field mandatory, set the | ||||
| :attr:`required` keyword argument of a field to ``True``. Fields also may have | ||||
| validation constraints available (such as :attr:`max_length` in the example | ||||
| above). Fields may also take default values, which will be used if a value is | ||||
| not provided. Default values may optionally be a callable, which will be called | ||||
| to retrieve the value (such as in the above example). The field types available  | ||||
| are as follows: | ||||
|  | ||||
| * :class:`~mongoengine.StringField` | ||||
| * :class:`~mongoengine.IntField` | ||||
| * :class:`~mongoengine.FloatField` | ||||
| * :class:`~mongoengine.DateTimeField` | ||||
| * :class:`~mongoengine.ListField` | ||||
| * :class:`~mongoengine.ObjectIdField` | ||||
| * :class:`~mongoengine.EmbeddedDocumentField` | ||||
| * :class:`~mongoengine.ReferenceField` | ||||
|  | ||||
| List fields | ||||
| ^^^^^^^^^^^ | ||||
| MongoDB allows the storage of lists of items. To add a list of items to a | ||||
| :class:`~mongoengine.Document`, use the :class:`~mongoengine.ListField` field | ||||
| type. :class:`~mongoengine.ListField` takes another field object as its first | ||||
| argument, which specifies which type elements may be stored within the list:: | ||||
|  | ||||
|     class Page(Document): | ||||
|         tags = ListField(StringField(max_length=50)) | ||||
|  | ||||
| Embedded documents | ||||
| ^^^^^^^^^^^^^^^^^^ | ||||
| MongoDB has the ability to embed documents within other documents. Schemata may | ||||
| be defined for these embedded documents, just as they may be for regular | ||||
| documents. To create an embedded document, just define a document as usual, but | ||||
| inherit from :class:`~mongoengine.EmbeddedDocument` rather than  | ||||
| :class:`~mongoengine.Document`:: | ||||
|  | ||||
|     class Comment(EmbeddedDocument): | ||||
|         content = StringField() | ||||
|  | ||||
| To embed the document within another document, use the | ||||
| :class:`~mongoengine.EmbeddedDocumentField` field type, providing the embedded | ||||
| document class as the first argument:: | ||||
|  | ||||
|     class Page(Document): | ||||
|         comments = ListField(EmbeddedDocumentField(Comment)) | ||||
|  | ||||
|     comment1 = Comment('Good work!') | ||||
|     comment2 = Comment('Nice article!') | ||||
|     page = Page(comments=[comment1, comment2]) | ||||
|  | ||||
| Reference fields | ||||
| ^^^^^^^^^^^^^^^^ | ||||
| References may be stored to other documents in the database using the | ||||
| :class:`~mongoengine.ReferenceField`. Pass in another document class as the | ||||
| first argument to the constructor, then simply assign document objects to the | ||||
| field:: | ||||
|      | ||||
|     class User(Document): | ||||
|         name = StringField() | ||||
|  | ||||
|     class Page(Document): | ||||
|         content = StringField() | ||||
|         author = ReferenceField(User) | ||||
|  | ||||
|     john = User(name="John Smith") | ||||
|     john.save() | ||||
|  | ||||
|     post = Page(content="Test Page") | ||||
|     post.author = john | ||||
|     post.save() | ||||
|  | ||||
| The :class:`User` object is automatically turned into a reference behind the | ||||
| scenes, and dereferenced when the :class:`Page` object is retrieved. | ||||
|  | ||||
| Uniqueness constraints | ||||
| ^^^^^^^^^^^^^^^^^^^^^^ | ||||
| MongoEngine allows you to specify that a field should be unique across a | ||||
| collection by providing ``unique=True`` to a :class:`~mongoengine.Field`\ 's | ||||
| constructor. If you try to save a document that has the same value for a unique | ||||
| field as a document that is already in the database, a  | ||||
| :class:`~mongoengine.OperationError` will be raised. You may also specify | ||||
| multi-field uniqueness constraints by using :attr:`unique_with`, which may be | ||||
| either a single field name, or a list or tuple of field names:: | ||||
|  | ||||
|     class User(Document): | ||||
|         username = StringField(unique=True) | ||||
|         first_name = StringField() | ||||
|         last_name = StringField(unique_with='last_name') | ||||
|  | ||||
| Document collections | ||||
| -------------------- | ||||
| Document classes that inherit **directly** from :class:`~mongoengine.Document` | ||||
| will have their own **collection** in the database. The name of the collection | ||||
| is by default the name of the class, coverted to lowercase (so in the example | ||||
| above, the collection would be called `page`). If you need to change the name | ||||
| of the collection (e.g. to use MongoEngine with an existing database), then | ||||
| create a class dictionary attribute called :attr:`meta` on your document, and | ||||
| set :attr:`collection` to the name of the collection that you want your | ||||
| document class to use:: | ||||
|  | ||||
|     class Page(Document): | ||||
|         title = StringField(max_length=200, required=True) | ||||
|         meta = {'collection': 'cmsPage'} | ||||
|  | ||||
| Capped collections | ||||
| ^^^^^^^^^^^^^^^^^^ | ||||
| A :class:`~mongoengine.Document` may use a **Capped Collection** by specifying | ||||
| :attr:`max_documents` and :attr:`max_size` in the :attr:`meta` dictionary. | ||||
| :attr:`max_documents` is the maximum number of documents that is allowed to be | ||||
| stored in the collection, and :attr:`max_size` is the maximum size of the | ||||
| collection in bytes. If :attr:`max_size` is not specified and | ||||
| :attr:`max_documents` is, :attr:`max_size` defaults to 10000000 bytes (10MB). | ||||
| The following example shows a :class:`Log` document that will be limited to  | ||||
| 1000 entries and 2MB of disk space:: | ||||
|  | ||||
|     class Log(Document): | ||||
|         ip_address = StringField() | ||||
|         meta = {'max_documents': 1000, 'max_size': 2000000} | ||||
|  | ||||
| Indexes | ||||
| ------- | ||||
| You can specify indexes on collections to make querying faster. This is done | ||||
| by creating a list of index specifications called :attr:`indexes` in the | ||||
| :attr:`~mongoengine.Document.meta` dictionary, where an index specification may | ||||
| either be a single field name, or a tuple containing multiple field names. A | ||||
| direction may be specified on fields by prefixing the field name with a **+** | ||||
| or a **-** sign. Note that direction only matters on multi-field indexes. :: | ||||
|  | ||||
|     class Page(Document): | ||||
|         title = StringField() | ||||
|         rating = StringField() | ||||
|         meta = { | ||||
|             'indexes': ['title', ('title', '-rating')] | ||||
|         } | ||||
|          | ||||
| Ordering | ||||
| -------- | ||||
| A default ordering can be specified for your | ||||
| :class:`~mongoengine.queryset.QuerySet` using the :attr:`ordering` attribute of | ||||
| :attr:`~mongoengine.Document.meta`.  Ordering will be applied when the | ||||
| :class:`~mongoengine.queryset.QuerySet` is created, and can be overridden by | ||||
| subsequent calls to :meth:`~mongoengine.queryset.QuerySet.order_by`. :: | ||||
|  | ||||
|     from datetime import datetime | ||||
|  | ||||
|     class BlogPost(Document): | ||||
|         title = StringField() | ||||
|         published_date = DateTimeField() | ||||
|  | ||||
|         meta = { | ||||
|             'ordering': ['-published_date'] | ||||
|         } | ||||
|  | ||||
|     blog_post_1 = BlogPost(title="Blog Post #1") | ||||
|     blog_post_1.published_date = datetime(2010, 1, 5, 0, 0 ,0)) | ||||
|  | ||||
|     blog_post_2 = BlogPost(title="Blog Post #2")  | ||||
|     blog_post_2.published_date = datetime(2010, 1, 6, 0, 0 ,0)) | ||||
|  | ||||
|     blog_post_3 = BlogPost(title="Blog Post #3") | ||||
|     blog_post_3.published_date = datetime(2010, 1, 7, 0, 0 ,0)) | ||||
|  | ||||
|     blog_post_1.save() | ||||
|     blog_post_2.save() | ||||
|     blog_post_3.save() | ||||
|  | ||||
|     # get the "first" BlogPost using default ordering | ||||
|     # from BlogPost.meta.ordering | ||||
|     latest_post = BlogPost.objects.first()  | ||||
|     self.assertEqual(latest_post.title, "Blog Post #3") | ||||
|  | ||||
|     # override default ordering, order BlogPosts by "published_date" | ||||
|     first_post = BlogPost.objects.order_by("+published_date").first() | ||||
|     self.assertEqual(first_post.title, "Blog Post #1") | ||||
|  | ||||
| Document inheritance | ||||
| -------------------- | ||||
| To create a specialised type of a :class:`~mongoengine.Document` you have | ||||
| defined, you may subclass it and add any extra fields or methods you may need. | ||||
| As this is new class is not a direct subclass of | ||||
| :class:`~mongoengine.Document`, it will not be stored in its own collection; it | ||||
| will use the same collection as its superclass uses. This allows for more | ||||
| convenient and efficient retrieval of related documents:: | ||||
|  | ||||
|     # Stored in a collection named 'page' | ||||
|     class Page(Document): | ||||
|         title = StringField(max_length=200, required=True) | ||||
|  | ||||
|     # Also stored in the collection named 'page' | ||||
|     class DatedPage(Page): | ||||
|         date = DateTimeField() | ||||
|  | ||||
| Working with existing data | ||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||||
| To enable correct retrieval of documents involved in this kind of heirarchy, | ||||
| two extra attributes are stored on each document in the database: :attr:`_cls` | ||||
| and :attr:`_types`. These are hidden from the user through the MongoEngine | ||||
| interface, but may not be present if you are trying to use MongoEngine with  | ||||
| an existing database. For this reason, you may disable this inheritance | ||||
| mechansim, removing the dependency of :attr:`_cls` and :attr:`_types`, enabling | ||||
| you to work with existing databases. To disable inheritance on a document | ||||
| class, set :attr:`allow_inheritance` to ``False`` in the :attr:`meta` | ||||
| dictionary:: | ||||
|  | ||||
|     # Will work with data in an existing collection named 'cmsPage' | ||||
|     class Page(Document): | ||||
|         title = StringField(max_length=200, required=True) | ||||
|         meta = { | ||||
|             'collection': 'cmsPage', | ||||
|             'allow_inheritance': False, | ||||
|         } | ||||
|  | ||||
| Documents instances | ||||
| =================== | ||||
| To create a new document object, create an instance of the relevant document | ||||
| class, providing values for its fields as its constructor keyword arguments. | ||||
| You may provide values for any of the fields on the document:: | ||||
|      | ||||
|     >>> page = Page(title="Test Page") | ||||
|     >>> page.title | ||||
|     'Test Page' | ||||
|  | ||||
| You may also assign values to the document's fields using standard object  | ||||
| attribute syntax:: | ||||
|  | ||||
|     >>> page.title = "Example Page" | ||||
|     >>> page.title | ||||
|     'Example Page' | ||||
|  | ||||
| Saving and deleting documents | ||||
| ----------------------------- | ||||
| To save the document to the database, call the | ||||
| :meth:`~mongoengine.Document.save` method. If the document does not exist in | ||||
| the database, it will be created. If it does already exist, it will be | ||||
| updated. | ||||
|  | ||||
| To delete a document, call the :meth:`~mongoengine.Document.delete` method. | ||||
| Note that this will only work if the document exists in the database and has a | ||||
| valide :attr:`id`. | ||||
|  | ||||
| Document IDs | ||||
| ------------ | ||||
| Each document in the database has a unique id. This may be accessed through the | ||||
| :attr:`id` attribute on :class:`~mongoengine.Document` objects. Usually, the id | ||||
| will be generated automatically by the database server when the object is save, | ||||
| meaning that you may only access the :attr:`id` field once a document has been | ||||
| saved:: | ||||
|  | ||||
|     >>> page = Page(title="Test Page") | ||||
|     >>> page.id | ||||
|     >>> page.save() | ||||
|     >>> page.id | ||||
|     ObjectId('123456789abcdef000000000') | ||||
|  | ||||
| Alternatively, you may define one of your own fields to be the document's | ||||
| "primary key" by providing ``primary_key=True`` as a keyword argument to a | ||||
| field's constructor. Under the hood, MongoEngine will use this field as the | ||||
| :attr:`id`; in fact :attr:`id` is actually aliased to your primary key field so | ||||
| you may still use :attr:`id` to access the primary key if you want:: | ||||
|  | ||||
|     >>> class User(Document): | ||||
|     ...     email = StringField(primary_key=True) | ||||
|     ...     name = StringField() | ||||
|     ... | ||||
|     >>> bob = User(email='bob@example.com', name='Bob') | ||||
|     >>> bob.save() | ||||
|     >>> bob.id == bob.email == 'bob@example.com' | ||||
|     True | ||||
|  | ||||
| .. note:: | ||||
|    If you define your own primary key field, the field implicitly becomes | ||||
|    required, so a :class:`ValidationError` will be thrown if you don't provide | ||||
|    it. | ||||
|  | ||||
| Querying the database | ||||
| ===================== | ||||
| :class:`~mongoengine.Document` classes have an :attr:`objects` attribute, which | ||||
| is used for accessing the objects in the database associated with the class. | ||||
| The :attr:`objects` attribute is actually a | ||||
| :class:`~mongoengine.queryset.QuerySetManager`, which creates and returns a new | ||||
| a new :class:`~mongoengine.queryset.QuerySet` object on access. The | ||||
| :class:`~mongoengine.queryset.QuerySet` object may may be iterated over to | ||||
| fetch documents from the database:: | ||||
|  | ||||
|     # Prints out the names of all the users in the database | ||||
|     for user in User.objects: | ||||
|         print user.name | ||||
|  | ||||
| Filtering queries | ||||
| ----------------- | ||||
| The query may be filtered by calling the | ||||
| :class:`~mongoengine.queryset.QuerySet` object with field lookup keyword  | ||||
| arguments. The keys in the keyword arguments correspond to fields on the | ||||
| :class:`~mongoengine.Document` you are querying:: | ||||
|  | ||||
|     # This will return a QuerySet that will only iterate over users whose | ||||
|     # 'country' field is set to 'uk' | ||||
|     uk_users = User.objects(country='uk') | ||||
|  | ||||
| Fields on embedded documents may also be referred to using field lookup syntax | ||||
| by using a double-underscore in place of the dot in object attribute access | ||||
| syntax:: | ||||
|      | ||||
|     # This will return a QuerySet that will only iterate over pages that have | ||||
|     # been written by a user whose 'country' field is set to 'uk' | ||||
|     uk_pages = Page.objects(author__country='uk') | ||||
|  | ||||
| Querying lists | ||||
| ^^^^^^^^^^^^^^ | ||||
| On most fields, this syntax will look up documents where the field specified | ||||
| matches the given value exactly, but when the field refers to a | ||||
| :class:`~mongoengine.ListField`, a single item may be provided, in which case | ||||
| lists that contain that item will be matched:: | ||||
|  | ||||
|     class Page(Document): | ||||
|         tags = ListField(StringField()) | ||||
|  | ||||
|     # This will match all pages that have the word 'coding' as an item in the | ||||
|     # 'tags' list | ||||
|     Page.objects(tags='coding') | ||||
|  | ||||
| Query operators | ||||
| --------------- | ||||
| Operators other than equality may also be used in queries; just attach the | ||||
| operator name to a key with a double-underscore:: | ||||
|      | ||||
|     # Only find users whose age is 18 or less | ||||
|     young_users = Users.objects(age__lte=18) | ||||
|  | ||||
| Available operators are as follows: | ||||
|  | ||||
| * ``neq`` -- not equal to | ||||
| * ``lt`` -- less than | ||||
| * ``lte`` -- less than or equal to | ||||
| * ``gt`` -- greater than | ||||
| * ``gte`` -- greater than or equal to | ||||
| * ``in`` -- value is in list (a list of values should be provided) | ||||
| * ``nin`` -- value is not in list (a list of values should be provided) | ||||
| * ``mod`` -- ``value % x == y``, where ``x`` and ``y`` are two provided values | ||||
| * ``all`` -- every item in array is in list of values provided | ||||
| * ``size`` -- the size of the array is  | ||||
| * ``exists`` -- value for field exists | ||||
|  | ||||
| Limiting and skipping results | ||||
| ----------------------------- | ||||
| Just as with traditional ORMs, you may limit the number of results returned, or | ||||
| skip a number or results in you query. | ||||
| :meth:`~mongoengine.queryset.QuerySet.limit` and | ||||
| :meth:`~mongoengine.queryset.QuerySet.skip` and methods are available on | ||||
| :class:`~mongoengine.queryset.QuerySet` objects, but the prefered syntax for | ||||
| achieving this is using array-slicing syntax:: | ||||
|  | ||||
|     # Only the first 5 people | ||||
|     users = User.objects[:5] | ||||
|  | ||||
|     # All except for the first 5 people | ||||
|     users = User.objects[5:] | ||||
|  | ||||
|     # 5 users, starting from the 10th user found | ||||
|     users = User.objects[10:15] | ||||
|  | ||||
| Aggregation | ||||
| ----------- | ||||
| MongoDB provides some aggregation methods out of the box, but there are not as | ||||
| many as you typically get with an RDBMS. MongoEngine provides a wrapper around | ||||
| the built-in methods and provides some of its own, which are implemented as | ||||
| Javascript code that is executed on the database server. | ||||
|  | ||||
| Counting results | ||||
| ^^^^^^^^^^^^^^^^ | ||||
| Just as with limiting and skipping results, there is a method on | ||||
| :class:`~mongoengine.queryset.QuerySet` objects --  | ||||
| :meth:`~mongoengine.queryset.QuerySet.count`, but there is also a more Pythonic | ||||
| way of achieving this:: | ||||
|  | ||||
|     num_users = len(User.objects) | ||||
|  | ||||
| Further aggregation | ||||
| ^^^^^^^^^^^^^^^^^^^ | ||||
| You may sum over the values of a specific field on documents using | ||||
| :meth:`~mongoengine.queryset.QuerySet.sum`:: | ||||
|  | ||||
|     yearly_expense = Employee.objects.sum('salary') | ||||
|  | ||||
| .. note:: | ||||
|    If the field isn't present on a document, that document will be ignored from | ||||
|    the sum. | ||||
|  | ||||
| To get the average (mean) of a field on a collection of documents, use | ||||
| :meth:`~mongoengine.queryset.QuerySet.average`:: | ||||
|  | ||||
|     mean_age = User.objects.average('age') | ||||
|  | ||||
| As MongoDB provides native lists, MongoEngine provides a helper method to get a | ||||
| dictionary of the frequencies of items in lists across an entire collection -- | ||||
| :meth:`~mongoengine.queryset.QuerySet.item_frequencies`. An example of its use | ||||
| would be generating "tag-clouds":: | ||||
|  | ||||
|     class Article(Document): | ||||
|         tag = ListField(StringField()) | ||||
|  | ||||
|     # After adding some tagged articles... | ||||
|     tag_freqs = Article.objects.item_frequencies('tag', normalize=True) | ||||
|  | ||||
|     from operator import itemgetter | ||||
|     top_tags = sorted(tag_freqs.items(), key=itemgetter(1), reverse=True)[:10] | ||||
|  | ||||
| Advanced queries | ||||
| ---------------- | ||||
| Sometimes calling a :class:`~mongoengine.queryset.QuerySet` object with keyword | ||||
| arguments can't fully express the query you want to use -- for example if you | ||||
| need to combine a number of constraints using *and* and *or*. This is made  | ||||
| possible in MongoEngine through the :class:`~mongoengine.queryset.Q` class. | ||||
| A :class:`~mongoengine.queryset.Q` object represents part of a query, and | ||||
| can be initialised using the same keyword-argument syntax you use to query | ||||
| documents. To build a complex query, you may combine  | ||||
| :class:`~mongoengine.queryset.Q` objects using the ``&`` (and) and ``|`` (or) | ||||
| operators. To use :class:`~mongoengine.queryset.Q` objects, pass them in | ||||
| as positional arguments to :attr:`Document.objects` when you filter it by | ||||
| calling it with keyword arguments:: | ||||
|  | ||||
|     # Get published posts | ||||
|     Post.objects(Q(published=True) | Q(publish_date__lte=datetime.now())) | ||||
|  | ||||
|     # Get top posts | ||||
|     Post.objects((Q(featured=True) & Q(hits__gte=1000)) | Q(hits__gte=5000)) | ||||
|  | ||||
| .. warning:: | ||||
|    Only use these advanced queries if absolutely necessary as they will execute | ||||
|    significantly slower than regular queries. This is because they are not | ||||
|    natively supported by MongoDB -- they are compiled to Javascript and sent | ||||
|    to the server for execution. | ||||
|  | ||||
| Atomic updates | ||||
| -------------- | ||||
| Documents may be updated atomically by using the | ||||
| :meth:`~mongoengine.queryset.QuerySet.update_one` and | ||||
| :meth:`~mongoengine.queryset.QuerySet.update` methods on a  | ||||
| :meth:`~mongoengine.queryset.QuerySet`. There are several different "modifiers" | ||||
| that you may use with these methods: | ||||
|  | ||||
| * ``set`` -- set a particular value | ||||
| * ``unset`` -- delete a particular value (since MongoDB v1.3+) | ||||
| * ``inc`` -- increment a value by a given amount | ||||
| * ``dec`` -- decrement a value by a given amount | ||||
| * ``push`` -- append a value to a list | ||||
| * ``push_all`` -- append several values to a list | ||||
| * ``pull`` -- remove a value from a list | ||||
| * ``pull_all`` -- remove several values from a list | ||||
|  | ||||
| The syntax for atomic updates is similar to the querying syntax, but the  | ||||
| modifier comes before the field, not after it:: | ||||
|      | ||||
|     >>> post = BlogPost(title='Test', page_views=0, tags=['database']) | ||||
|     >>> post.save() | ||||
|     >>> BlogPost.objects(id=post.id).update_one(inc__page_views=1) | ||||
|     >>> post.reload()  # the document has been changed, so we need to reload it | ||||
|     >>> post.page_views | ||||
|     1 | ||||
|     >>> BlogPost.objects(id=post.id).update_one(set__title='Example Post') | ||||
|     >>> post.reload() | ||||
|     >>> post.title | ||||
|     'Example Post' | ||||
|     >>> BlogPost.objects(id=post.id).update_one(push__tags='nosql') | ||||
|     >>> post.reload() | ||||
|     >>> post.tags | ||||
|     ['database', 'nosql'] | ||||
|  | ||||
| @@ -12,7 +12,7 @@ __all__ = (document.__all__ + fields.__all__ + connection.__all__ + | ||||
|  | ||||
| __author__ = 'Harry Marr' | ||||
|  | ||||
| VERSION = (0, 2, 1) | ||||
| VERSION = (0, 3, 0) | ||||
|  | ||||
| def get_version(): | ||||
|     version = '%s.%s' % (VERSION[0], VERSION[1]) | ||||
|   | ||||
| @@ -3,6 +3,12 @@ from queryset import QuerySet, QuerySetManager | ||||
| import pymongo | ||||
|  | ||||
|  | ||||
| _document_registry = {} | ||||
|  | ||||
| def get_document(name): | ||||
|     return _document_registry[name] | ||||
|  | ||||
|  | ||||
| class ValidationError(Exception): | ||||
|     pass | ||||
|  | ||||
| @@ -12,9 +18,17 @@ class BaseField(object): | ||||
|     may be added to subclasses of `Document` to define a document's schema. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, name=None, required=False, default=None, unique=False, | ||||
|                  unique_with=None, primary_key=False): | ||||
|         self.name = name if not primary_key else '_id' | ||||
|     # Fields may have _types inserted into indexes by default  | ||||
|     _index_with_types = True | ||||
|      | ||||
|     def __init__(self, db_field=None, name=None, required=False, default=None,  | ||||
|                  unique=False, unique_with=None, primary_key=False): | ||||
|         self.db_field = (db_field or name) if not primary_key else '_id' | ||||
|         if name: | ||||
|             import warnings | ||||
|             msg = "Fields' 'name' attribute deprecated in favour of 'db_field'" | ||||
|             warnings.warn(msg, DeprecationWarning) | ||||
|         self.name = None | ||||
|         self.required = required or primary_key | ||||
|         self.default = default | ||||
|         self.unique = bool(unique or unique_with) | ||||
| @@ -53,7 +67,7 @@ class BaseField(object): | ||||
|         """ | ||||
|         return self.to_python(value) | ||||
|  | ||||
|     def prepare_query_value(self, value): | ||||
|     def prepare_query_value(self, op, value): | ||||
|         """Prepare a value that is being used in a query for PyMongo. | ||||
|         """ | ||||
|         return value | ||||
| @@ -69,14 +83,19 @@ class ObjectIdField(BaseField): | ||||
|     """ | ||||
|      | ||||
|     def to_python(self, value): | ||||
|         return str(value) | ||||
|         return value | ||||
|         # return unicode(value) | ||||
|  | ||||
|     def to_mongo(self, value): | ||||
|         if not isinstance(value, pymongo.objectid.ObjectId): | ||||
|             return pymongo.objectid.ObjectId(str(value)) | ||||
|             try: | ||||
|                 return pymongo.objectid.ObjectId(str(value)) | ||||
|             except Exception, e: | ||||
|                 #e.message attribute has been deprecated since Python 2.6 | ||||
|                 raise ValidationError(str(e)) | ||||
|         return value | ||||
|  | ||||
|     def prepare_query_value(self, value): | ||||
|     def prepare_query_value(self, op, value): | ||||
|         return self.to_mongo(value) | ||||
|  | ||||
|     def validate(self, value): | ||||
| @@ -99,6 +118,7 @@ class DocumentMetaclass(type): | ||||
|         doc_fields = {} | ||||
|         class_name = [name] | ||||
|         superclasses = {} | ||||
|         simple_class = True | ||||
|         for base in bases: | ||||
|             # Include all fields present in superclasses | ||||
|             if hasattr(base, '_fields'): | ||||
| @@ -107,19 +127,47 @@ class DocumentMetaclass(type): | ||||
|                 # Get superclasses from superclass | ||||
|                 superclasses[base._class_name] = base | ||||
|                 superclasses.update(base._superclasses) | ||||
|                  | ||||
|             if hasattr(base, '_meta'): | ||||
|                 # Ensure that the Document class may be subclassed -  | ||||
|                 # inheritance may be disabled to remove dependency on  | ||||
|                 # additional fields _cls and _types | ||||
|                 if base._meta.get('allow_inheritance', True) == False: | ||||
|                     raise ValueError('Document %s may not be subclassed' % | ||||
|                                      base.__name__) | ||||
|                 else: | ||||
|                     simple_class = False | ||||
|  | ||||
|         meta = attrs.get('_meta', attrs.get('meta', {})) | ||||
|  | ||||
|         if 'allow_inheritance' not in meta: | ||||
|             meta['allow_inheritance'] = True | ||||
|  | ||||
|         # Only simple classes - direct subclasses of Document - may set | ||||
|         # allow_inheritance to False | ||||
|         if not simple_class and not meta['allow_inheritance']: | ||||
|             raise ValueError('Only direct subclasses of Document may set ' | ||||
|                              '"allow_inheritance" to False') | ||||
|         attrs['_meta'] = meta | ||||
|  | ||||
|         attrs['_class_name'] = '.'.join(reversed(class_name)) | ||||
|         attrs['_superclasses'] = superclasses | ||||
|  | ||||
|         # Add the document's fields to the _fields attribute | ||||
|         for attr_name, attr_value in attrs.items(): | ||||
|             if hasattr(attr_value, "__class__") and \ | ||||
|                 issubclass(attr_value.__class__, BaseField): | ||||
|                 if not attr_value.name: | ||||
|                     attr_value.name = attr_name | ||||
|                issubclass(attr_value.__class__, BaseField): | ||||
|                 attr_value.name = attr_name | ||||
|                 if not attr_value.db_field: | ||||
|                     attr_value.db_field = attr_name | ||||
|                 doc_fields[attr_name] = attr_value | ||||
|         attrs['_fields'] = doc_fields | ||||
|  | ||||
|         return super_new(cls, name, bases, attrs) | ||||
|         new_class = super_new(cls, name, bases, attrs) | ||||
|         for field in new_class._fields.values(): | ||||
|             field.owner_document = new_class | ||||
|  | ||||
|         return new_class | ||||
|  | ||||
|  | ||||
| class TopLevelDocumentMetaclass(DocumentMetaclass): | ||||
| @@ -128,6 +176,8 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): | ||||
|     """ | ||||
|  | ||||
|     def __new__(cls, name, bases, attrs): | ||||
|         global _document_registry | ||||
|  | ||||
|         super_new = super(TopLevelDocumentMetaclass, cls).__new__ | ||||
|         # Classes defined in this package are abstract and should not have  | ||||
|         # their own metadata with DB collection, etc. | ||||
| @@ -139,21 +189,12 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): | ||||
|  | ||||
|         collection = name.lower() | ||||
|          | ||||
|         simple_class = True | ||||
|         id_field = None | ||||
|         base_indexes = [] | ||||
|  | ||||
|         # Subclassed documents inherit collection from superclass | ||||
|         for base in bases: | ||||
|             if hasattr(base, '_meta') and 'collection' in base._meta: | ||||
|                 # Ensure that the Document class may be subclassed -  | ||||
|                 # inheritance may be disabled to remove dependency on  | ||||
|                 # additional fields _cls and _types | ||||
|                 if base._meta.get('allow_inheritance', True) == False: | ||||
|                     raise ValueError('Document %s may not be subclassed' % | ||||
|                                      base.__name__) | ||||
|                 else: | ||||
|                     simple_class = False | ||||
|                 collection = base._meta['collection'] | ||||
|  | ||||
|                 id_field = id_field or base._meta.get('id_field') | ||||
| @@ -161,7 +202,6 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): | ||||
|  | ||||
|         meta = { | ||||
|             'collection': collection, | ||||
|             'allow_inheritance': True, | ||||
|             'max_documents': None, | ||||
|             'max_size': None, | ||||
|             'ordering': [], # default ordering applied at runtime | ||||
| @@ -171,14 +211,6 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): | ||||
|  | ||||
|         # Apply document-defined meta options | ||||
|         meta.update(attrs.get('meta', {})) | ||||
|  | ||||
|         meta['indexes'] += base_indexes | ||||
|          | ||||
|         # Only simple classes - direct subclasses of Document - may set | ||||
|         # allow_inheritance to False | ||||
|         if not simple_class and not meta['allow_inheritance']: | ||||
|             raise ValueError('Only direct subclasses of Document may set ' | ||||
|                              '"allow_inheritance" to False') | ||||
|         attrs['_meta'] = meta | ||||
|  | ||||
|         # Set up collection manager, needs the class to have fields so use | ||||
| @@ -186,6 +218,10 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): | ||||
|         new_class = super_new(cls, name, bases, attrs) | ||||
|         new_class.objects = QuerySetManager() | ||||
|  | ||||
|         user_indexes = [QuerySet._build_index_spec(new_class, spec) | ||||
|                         for spec in meta['indexes']] + base_indexes | ||||
|         new_class._meta['indexes'] = user_indexes | ||||
|          | ||||
|         unique_indexes = [] | ||||
|         for field_name, field in new_class._fields.items(): | ||||
|             # Generate a list of indexes needed by uniqueness constraints | ||||
| @@ -204,7 +240,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): | ||||
|                         parts = other_name.split('.') | ||||
|                         # Lookup real name | ||||
|                         parts = QuerySet._lookup_field(new_class, parts) | ||||
|                         name_parts = [part.name for part in parts] | ||||
|                         name_parts = [part.db_field for part in parts] | ||||
|                         unique_with.append('.'.join(name_parts)) | ||||
|                         # Unique field should be required | ||||
|                         parts[-1].required = True | ||||
| @@ -228,7 +264,10 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): | ||||
|  | ||||
|         if not new_class._meta['id_field']: | ||||
|             new_class._meta['id_field'] = 'id' | ||||
|             new_class.id = new_class._fields['id'] = ObjectIdField(name='_id') | ||||
|             new_class._fields['id'] = ObjectIdField(db_field='_id') | ||||
|             new_class.id = new_class._fields['id'] | ||||
|  | ||||
|         _document_registry[name] = new_class | ||||
|  | ||||
|         return new_class | ||||
|  | ||||
| @@ -246,6 +285,25 @@ class BaseDocument(object): | ||||
|                 value = getattr(self, attr_name, None) | ||||
|                 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 | ||||
|     def _get_subclasses(cls): | ||||
|         """Return a dictionary of all subclasses (found recursively). | ||||
| @@ -311,7 +369,7 @@ class BaseDocument(object): | ||||
|         for field_name, field in self._fields.items(): | ||||
|             value = getattr(self, field_name, None) | ||||
|             if value is not None: | ||||
|                 data[field.name] = field.to_mongo(value) | ||||
|                 data[field.db_field] = field.to_mongo(value) | ||||
|         # Only add _cls and _types if allow_inheritance is not False | ||||
|         if not (hasattr(self, '_meta') and | ||||
|                 self._meta.get('allow_inheritance', True) == False): | ||||
| @@ -321,7 +379,7 @@ class BaseDocument(object): | ||||
|      | ||||
|     @classmethod | ||||
|     def _from_son(cls, son): | ||||
|         """Create an instance of a Document (subclass) from a PyMongo SOM. | ||||
|         """Create an instance of a Document (subclass) from a PyMongo SON. | ||||
|         """ | ||||
|         # get the class name from the document, falling back to the given | ||||
|         # class if unavailable | ||||
| @@ -344,8 +402,18 @@ class BaseDocument(object): | ||||
|                 return None | ||||
|             cls = subclasses[class_name] | ||||
|  | ||||
|         for field_name, field in cls._fields.items(): | ||||
|             if field.name in data: | ||||
|                 data[field_name] = field.to_python(data[field.name]) | ||||
|         present_fields = data.keys() | ||||
|  | ||||
|         return cls(**data) | ||||
|         for field_name, field in cls._fields.items(): | ||||
|             if field.db_field in data: | ||||
|                 data[field_name] = field.to_python(data[field.db_field]) | ||||
|  | ||||
|         obj = cls(**data) | ||||
|         obj._present_fields = present_fields | ||||
|         return obj | ||||
|      | ||||
|     def __eq__(self, other): | ||||
|         if isinstance(other, self.__class__) and hasattr(other, 'id'): | ||||
|             if self.id == other.id: | ||||
|                 return True | ||||
|         return False | ||||
|   | ||||
| @@ -7,7 +7,6 @@ __all__ = ['ConnectionError', 'connect'] | ||||
| _connection_settings = { | ||||
|     'host': 'localhost', | ||||
|     'port': 27017, | ||||
|     'pool_size': 1, | ||||
| } | ||||
| _connection = None | ||||
|  | ||||
| @@ -60,3 +59,4 @@ def connect(db, username=None, password=None, **kwargs): | ||||
|     _db_name = db | ||||
|     _db_username = username | ||||
|     _db_password = password | ||||
|     return _get_db() | ||||
| @@ -30,6 +30,7 @@ class User(Document): | ||||
|     is_active = BooleanField(default=True) | ||||
|     is_superuser = BooleanField(default=False) | ||||
|     last_login = DateTimeField(default=datetime.datetime.now) | ||||
|     date_joined = DateTimeField(default=datetime.datetime.now) | ||||
|  | ||||
|     def get_full_name(self): | ||||
|         """Returns the users first and last names, separated by a space. | ||||
| @@ -53,6 +54,8 @@ class User(Document): | ||||
|         salt = get_hexdigest(algo, str(random()), str(random()))[:5] | ||||
|         hash = get_hexdigest(algo, salt, raw_password) | ||||
|         self.password = '%s$%s$%s' % (algo, salt, hash) | ||||
|         self.save() | ||||
|         return self | ||||
|  | ||||
|     def check_password(self, raw_password): | ||||
|         """Checks the user's password against a provided password - always use | ||||
| @@ -68,11 +71,27 @@ class User(Document): | ||||
|         """Create (and save) a new user with the given username, password and | ||||
|         email address. | ||||
|         """ | ||||
|         user = User(username=username, email=email) | ||||
|         now = datetime.datetime.now() | ||||
|          | ||||
|         # Normalize the address by lowercasing the domain part of the email | ||||
|         # address. | ||||
|         # Not sure why we'r allowing null email when its not allowed in django | ||||
|         if email is not None: | ||||
|             try: | ||||
|                 email_name, domain_part = email.strip().split('@', 1) | ||||
|             except ValueError: | ||||
|                 pass | ||||
|             else: | ||||
|                 email = '@'.join([email_name, domain_part.lower()]) | ||||
|              | ||||
|         user = User(username=username, email=email, date_joined=now) | ||||
|         user.set_password(password) | ||||
|         user.save() | ||||
|         return user | ||||
|      | ||||
|     def get_and_delete_messages(self): | ||||
|         return [] | ||||
|  | ||||
|  | ||||
| class MongoEngineBackend(object): | ||||
|     """Authenticate using MongoEngine and mongoengine.django.auth.User. | ||||
|   | ||||
| @@ -78,9 +78,9 @@ class Document(BaseDocument): | ||||
|                 object_id = collection.save(doc, safe=safe) | ||||
|         except pymongo.errors.OperationFailure, err: | ||||
|             message = 'Could not save document (%s)' | ||||
|             if 'duplicate key' in str(err): | ||||
|                 message = 'Tried to save duplicate unique keys (%s)' | ||||
|             raise OperationError(message % str(err)) | ||||
|             if u'duplicate key' in unicode(err): | ||||
|                 message = u'Tried to save duplicate unique keys (%s)' | ||||
|             raise OperationError(message % unicode(err)) | ||||
|         id_field = self._meta['id_field'] | ||||
|         self[id_field] = self._fields[id_field].to_python(object_id) | ||||
|  | ||||
| @@ -95,7 +95,8 @@ class Document(BaseDocument): | ||||
|         try: | ||||
|             self.__class__.objects(**{id_field: object_id}).delete(safe=safe) | ||||
|         except pymongo.errors.OperationFailure, err: | ||||
|             raise OperationError('Could not delete document (%s)' % str(err)) | ||||
|             message = u'Could not delete document (%s)' % err.message | ||||
|             raise OperationError(message) | ||||
|  | ||||
|     def reload(self): | ||||
|         """Reloads all attributes from the database. | ||||
| @@ -107,25 +108,6 @@ class Document(BaseDocument): | ||||
|         for field in self._fields: | ||||
|             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 | ||||
|     def drop_collection(cls): | ||||
|         """Drops the entire collection associated with this | ||||
| @@ -133,3 +115,43 @@ class Document(BaseDocument): | ||||
|         """ | ||||
|         db = _get_db() | ||||
|         db.drop_collection(cls._meta['collection']) | ||||
|  | ||||
|  | ||||
| class MapReduceDocument(object): | ||||
|     """A document returned from a map/reduce query. | ||||
|      | ||||
|     :param collection: An instance of :class:`~pymongo.Collection` | ||||
|     :param key: Document/result key, often an instance of  | ||||
|                 :class:`~pymongo.objectid.ObjectId`. If supplied as  | ||||
|                 an ``ObjectId`` found in the given ``collection``,  | ||||
|                 the object can be accessed via the ``object`` property. | ||||
|     :param value: The result(s) for this key. | ||||
|      | ||||
|     .. versionadded:: 0.3 | ||||
|     """ | ||||
|      | ||||
|     def __init__(self, document, collection, key, value): | ||||
|         self._document = document | ||||
|         self._collection = collection | ||||
|         self.key = key | ||||
|         self.value = value | ||||
|      | ||||
|     @property | ||||
|     def object(self): | ||||
|         """Lazy-load the object referenced by ``self.key``. ``self.key``  | ||||
|         should be the ``primary_key``. | ||||
|         """ | ||||
|         id_field = self._document()._meta['id_field'] | ||||
|         id_field_type = type(id_field) | ||||
|          | ||||
|         if not isinstance(self.key, id_field_type): | ||||
|             try: | ||||
|                 self.key = id_field_type(self.key) | ||||
|             except: | ||||
|                 raise Exception("Could not cast key as %s" % \ | ||||
|                                 id_field_type.__name__) | ||||
|  | ||||
|         if not hasattr(self, "_key_object"): | ||||
|             self._key_object = self._document.objects.with_id(self.key) | ||||
|             return self._key_object | ||||
|         return self._key_object | ||||
|   | ||||
| @@ -1,15 +1,20 @@ | ||||
| from base import BaseField, ObjectIdField, ValidationError | ||||
| from base import BaseField, ObjectIdField, ValidationError, get_document | ||||
| from document import Document, EmbeddedDocument | ||||
| from connection import _get_db | ||||
|  | ||||
| import re | ||||
| import pymongo | ||||
| import datetime | ||||
| import decimal | ||||
|  | ||||
|  | ||||
| __all__ = ['StringField', 'IntField', 'FloatField', 'BooleanField', | ||||
|            'DateTimeField', 'EmbeddedDocumentField', 'ListField',  | ||||
|            'ObjectIdField', 'ReferenceField', 'ValidationError'] | ||||
|            'DateTimeField', 'EmbeddedDocumentField', 'ListField', 'DictField', | ||||
|            'ObjectIdField', 'ReferenceField', 'ValidationError', | ||||
|            'DecimalField', 'URLField', 'GenericReferenceField', | ||||
|            'BinaryField'] | ||||
|  | ||||
| RECURSIVE_REFERENCE_CONSTANT = 'self' | ||||
|  | ||||
|  | ||||
| class StringField(BaseField): | ||||
| @@ -37,6 +42,57 @@ class StringField(BaseField): | ||||
|     def lookup_member(self, member_name): | ||||
|         return None | ||||
|  | ||||
|     def prepare_query_value(self, op, value): | ||||
|         if not isinstance(op, basestring): | ||||
|             return value | ||||
|  | ||||
|         if op.lstrip('i') in ('startswith', 'endswith', 'contains'): | ||||
|             flags = 0 | ||||
|             if op.startswith('i'): | ||||
|                 flags = re.IGNORECASE | ||||
|                 op = op.lstrip('i') | ||||
|  | ||||
|             regex = r'%s' | ||||
|             if op == 'startswith': | ||||
|                 regex = r'^%s' | ||||
|             elif op == 'endswith': | ||||
|                 regex = r'%s$' | ||||
|             value = re.compile(regex % value, flags) | ||||
|         return value | ||||
|  | ||||
|  | ||||
| class URLField(StringField): | ||||
|     """A field that validates input as a URL. | ||||
|  | ||||
|     .. 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 | ||||
|     ) | ||||
|  | ||||
|     def __init__(self, verify_exists=False, **kwargs): | ||||
|         self.verify_exists = verify_exists | ||||
|         super(URLField, self).__init__(**kwargs) | ||||
|  | ||||
|     def validate(self, value): | ||||
|         if not URLField.URL_REGEX.match(value): | ||||
|             raise ValidationError('Invalid URL: %s' % value) | ||||
|  | ||||
|         if self.verify_exists: | ||||
|             import urllib2 | ||||
|             try: | ||||
|                 request = urllib2.Request(value) | ||||
|                 response = urllib2.urlopen(request) | ||||
|             except Exception, e: | ||||
|                 message = 'This URL appears to be a broken link: %s' % e | ||||
|                 raise ValidationError(message) | ||||
|  | ||||
|  | ||||
| class IntField(BaseField): | ||||
|     """An integer field. | ||||
| @@ -50,7 +106,10 @@ class IntField(BaseField): | ||||
|         return int(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: | ||||
|             raise ValidationError('Integer value is too small') | ||||
| @@ -71,6 +130,8 @@ class FloatField(BaseField): | ||||
|         return float(value) | ||||
|  | ||||
|     def validate(self, value): | ||||
|         if isinstance(value, int): | ||||
|             value = float(value) | ||||
|         assert isinstance(value, float) | ||||
|  | ||||
|         if self.min_value is not None and value < self.min_value: | ||||
| @@ -80,6 +141,37 @@ class FloatField(BaseField): | ||||
|             raise ValidationError('Float value is too large') | ||||
|  | ||||
|  | ||||
| class DecimalField(BaseField): | ||||
|     """A fixed-point decimal number field. | ||||
|  | ||||
|     .. versionadded:: 0.3 | ||||
|     """ | ||||
|  | ||||
|     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 value > self.max_value: | ||||
|             raise ValidationError('Decimal value is too large') | ||||
|  | ||||
|  | ||||
| class BooleanField(BaseField): | ||||
|     """A boolean field type. | ||||
|  | ||||
| @@ -129,11 +221,12 @@ class EmbeddedDocumentField(BaseField): | ||||
|         if not isinstance(value, self.document): | ||||
|             raise ValidationError('Invalid embedded document instance ' | ||||
|                                   'provided to an EmbeddedDocumentField') | ||||
|         self.document.validate(value) | ||||
|  | ||||
|     def lookup_member(self, member_name): | ||||
|         return self.document._fields.get(member_name) | ||||
|  | ||||
|     def prepare_query_value(self, value): | ||||
|     def prepare_query_value(self, op, value): | ||||
|         return self.to_mongo(value) | ||||
|  | ||||
|  | ||||
| @@ -142,6 +235,9 @@ class ListField(BaseField): | ||||
|     of the field to be used as a list in the database. | ||||
|     """ | ||||
|  | ||||
|     # ListFields cannot be indexed with _types - MongoDB doesn't support this | ||||
|     _index_with_types = False | ||||
|  | ||||
|     def __init__(self, field, **kwargs): | ||||
|         if not isinstance(field, BaseField): | ||||
|             raise ValidationError('Argument to ListField constructor must be ' | ||||
| @@ -149,6 +245,42 @@ class ListField(BaseField): | ||||
|         self.field = field | ||||
|         super(ListField, self).__init__(**kwargs) | ||||
|  | ||||
|     def __get__(self, instance, owner): | ||||
|         """Descriptor to automatically dereference references. | ||||
|         """ | ||||
|         if instance is None: | ||||
|             # Document class being used rather than a document object | ||||
|             return self | ||||
|  | ||||
|         if isinstance(self.field, ReferenceField): | ||||
|             referenced_type = self.field.document_type | ||||
|             # Get value from document instance if available  | ||||
|             value_list = instance._data.get(self.name) | ||||
|             if value_list: | ||||
|                 deref_list = [] | ||||
|                 for value in value_list: | ||||
|                     # Dereference DBRefs | ||||
|                     if isinstance(value, (pymongo.dbref.DBRef)): | ||||
|                         value = _get_db().dereference(value) | ||||
|                         deref_list.append(referenced_type._from_son(value)) | ||||
|                     else: | ||||
|                         deref_list.append(value) | ||||
|                 instance._data[self.name] = deref_list | ||||
|  | ||||
|         if isinstance(self.field, GenericReferenceField): | ||||
|             value_list = instance._data.get(self.name) | ||||
|             if value_list: | ||||
|                 deref_list = [] | ||||
|                 for value in value_list: | ||||
|                     # Dereference DBRefs | ||||
|                     if isinstance(value, (dict, pymongo.son.SON)): | ||||
|                         deref_list.append(self.field.dereference(value)) | ||||
|                     else: | ||||
|                         deref_list.append(value) | ||||
|                 instance._data[self.name] = deref_list | ||||
|  | ||||
|         return super(ListField, self).__get__(instance, owner) | ||||
|  | ||||
|     def to_python(self, value): | ||||
|         return [self.field.to_python(item) for item in value] | ||||
|  | ||||
| @@ -164,30 +296,63 @@ class ListField(BaseField): | ||||
|  | ||||
|         try: | ||||
|             [self.field.validate(item) for item in value] | ||||
|         except: | ||||
|             raise ValidationError('All items in a list field must be of the ' | ||||
|                                   'specified type') | ||||
|         except Exception, err: | ||||
|             raise ValidationError('Invalid ListField item (%s)' % str(err)) | ||||
|  | ||||
|     def prepare_query_value(self, value): | ||||
|     def prepare_query_value(self, op, value): | ||||
|         if op in ('set', 'unset'): | ||||
|             return [self.field.to_mongo(v) for v in value] | ||||
|         return self.field.to_mongo(value) | ||||
|  | ||||
|     def lookup_member(self, member_name): | ||||
|         return self.field.lookup_member(member_name) | ||||
|  | ||||
|  | ||||
| class DictField(BaseField): | ||||
|     """A dictionary field that wraps a standard Python dictionary. This is | ||||
|     similar to an embedded document, but the structure is not defined. | ||||
|  | ||||
|     .. versionadded:: 0.3 | ||||
|     """ | ||||
|  | ||||
|     def validate(self, value): | ||||
|         """Make sure that a list of valid fields is being used. | ||||
|         """ | ||||
|         if not isinstance(value, dict): | ||||
|             raise ValidationError('Only dictionaries may be used in a ' | ||||
|                                   'DictField') | ||||
|  | ||||
|         if any(('.' in k or '$' in k) for k in value): | ||||
|             raise ValidationError('Invalid dictionary key name - keys may not ' | ||||
|                                   'contain "." or "$" characters') | ||||
|  | ||||
|     def lookup_member(self, member_name): | ||||
|         return BaseField(db_field=member_name) | ||||
|  | ||||
|  | ||||
| class ReferenceField(BaseField): | ||||
|     """A reference to a document that will be automatically dereferenced on | ||||
|     access (lazily). | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, document_type, **kwargs): | ||||
|         if not issubclass(document_type, Document): | ||||
|             raise ValidationError('Argument to ReferenceField constructor ' | ||||
|                                   'must be a top level document class') | ||||
|         self.document_type = document_type | ||||
|         if not isinstance(document_type, basestring): | ||||
|             if not issubclass(document_type, (Document, basestring)): | ||||
|                 raise ValidationError('Argument to ReferenceField constructor ' | ||||
|                                       'must be a document class or a string') | ||||
|         self.document_type_obj = document_type | ||||
|         self.document_obj = None | ||||
|         super(ReferenceField, self).__init__(**kwargs) | ||||
|  | ||||
|     @property | ||||
|     def document_type(self): | ||||
|         if isinstance(self.document_type_obj, basestring): | ||||
|             if self.document_type_obj == RECURSIVE_REFERENCE_CONSTANT: | ||||
|                 self.document_type_obj = self.owner_document | ||||
|             else: | ||||
|                 self.document_type_obj = get_document(self.document_type_obj) | ||||
|         return self.document_type_obj | ||||
|  | ||||
|     def __get__(self, instance, owner): | ||||
|         """Descriptor to allow lazy dereferencing. | ||||
|         """ | ||||
| @@ -206,24 +371,23 @@ class ReferenceField(BaseField): | ||||
|         return super(ReferenceField, self).__get__(instance, owner) | ||||
|  | ||||
|     def to_mongo(self, document): | ||||
|         if isinstance(document, (str, unicode, pymongo.objectid.ObjectId)): | ||||
|             # document may already be an object id | ||||
|             id_ = document | ||||
|         else: | ||||
|         id_field_name = self.document_type._meta['id_field'] | ||||
|         id_field = self.document_type._fields[id_field_name] | ||||
|  | ||||
|         if isinstance(document, Document): | ||||
|             # We need the id from the saved object to create the DBRef | ||||
|             id_ = document.id | ||||
|             if id_ is None: | ||||
|                 raise ValidationError('You can only reference documents once ' | ||||
|                                       'they have been saved to the database') | ||||
|         else: | ||||
|             id_ = document | ||||
|  | ||||
|         # id may be a string rather than an ObjectID object | ||||
|         if not isinstance(id_, pymongo.objectid.ObjectId): | ||||
|             id_ = pymongo.objectid.ObjectId(id_) | ||||
|  | ||||
|         id_ = id_field.to_mongo(id_) | ||||
|         collection = self.document_type._meta['collection'] | ||||
|         return pymongo.dbref.DBRef(collection, id_) | ||||
|  | ||||
|     def prepare_query_value(self, value): | ||||
|     def prepare_query_value(self, op, value): | ||||
|         return self.to_mongo(value) | ||||
|  | ||||
|     def validate(self, value): | ||||
| @@ -231,3 +395,70 @@ class ReferenceField(BaseField): | ||||
|  | ||||
|     def lookup_member(self, member_name): | ||||
|         return self.document_type._fields.get(member_name) | ||||
|  | ||||
|  | ||||
| class GenericReferenceField(BaseField): | ||||
|     """A reference to *any* :class:`~mongoengine.document.Document` subclass | ||||
|     that will be automatically dereferenced on access (lazily). | ||||
|  | ||||
|     .. versionadded:: 0.3 | ||||
|     """ | ||||
|  | ||||
|     def __get__(self, instance, owner): | ||||
|         if instance is None: | ||||
|             return self | ||||
|  | ||||
|         value = instance._data.get(self.name) | ||||
|         if isinstance(value, (dict, pymongo.son.SON)): | ||||
|             instance._data[self.name] = self.dereference(value) | ||||
|  | ||||
|         return super(GenericReferenceField, self).__get__(instance, owner) | ||||
|  | ||||
|     def dereference(self, value): | ||||
|         doc_cls = get_document(value['_cls']) | ||||
|         reference = value['_ref'] | ||||
|         doc = _get_db().dereference(reference) | ||||
|         if doc is not None: | ||||
|             doc = doc_cls._from_son(doc) | ||||
|         return doc | ||||
|  | ||||
|     def to_mongo(self, document): | ||||
|         id_field_name = document.__class__._meta['id_field'] | ||||
|         id_field = document.__class__._fields[id_field_name] | ||||
|  | ||||
|         if isinstance(document, Document): | ||||
|             # We need the id from the saved object to create the DBRef | ||||
|             id_ = document.id | ||||
|             if id_ is None: | ||||
|                 raise ValidationError('You can only reference documents once ' | ||||
|                                       'they have been saved to the database') | ||||
|         else: | ||||
|             id_ = document | ||||
|  | ||||
|         id_ = id_field.to_mongo(id_) | ||||
|         collection = document._meta['collection'] | ||||
|         ref = pymongo.dbref.DBRef(collection, id_) | ||||
|         return {'_cls': document.__class__.__name__, '_ref': ref} | ||||
|  | ||||
|     def prepare_query_value(self, op, value): | ||||
|         return self.to_mongo(value)['_ref'] | ||||
|  | ||||
| class BinaryField(BaseField): | ||||
|     """A binary data field. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, max_bytes=None, **kwargs): | ||||
|         self.max_bytes = max_bytes | ||||
|         super(BinaryField, self).__init__(**kwargs) | ||||
|  | ||||
|     def to_mongo(self, value): | ||||
|         return pymongo.binary.Binary(value) | ||||
|  | ||||
|     def to_python(self, value): | ||||
|         return str(value) | ||||
|  | ||||
|     def validate(self, value): | ||||
|         assert isinstance(value, str) | ||||
|  | ||||
|         if self.max_bytes is not None and len(value) > self.max_bytes: | ||||
|             raise ValidationError('Binary value is too long') | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| from connection import _get_db | ||||
|  | ||||
| import pymongo | ||||
| import re | ||||
| import copy | ||||
|  | ||||
|  | ||||
| @@ -11,6 +12,14 @@ __all__ = ['queryset_manager', 'Q', 'InvalidQueryError', | ||||
| REPR_OUTPUT_SIZE = 20 | ||||
|  | ||||
|  | ||||
| class DoesNotExist(Exception): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class MultipleObjectsReturned(Exception): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class InvalidQueryError(Exception): | ||||
|     pass | ||||
|  | ||||
| @@ -19,25 +28,30 @@ class OperationError(Exception): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| RE_TYPE = type(re.compile('')) | ||||
|  | ||||
|  | ||||
| class Q(object): | ||||
|  | ||||
|     OR = '||' | ||||
|     AND = '&&' | ||||
|     OPERATORS = { | ||||
|         'eq': 'this.%(field)s == %(value)s', | ||||
|         'neq': 'this.%(field)s != %(value)s', | ||||
|         'ne': 'this.%(field)s != %(value)s', | ||||
|         'gt': 'this.%(field)s > %(value)s', | ||||
|         'gte': 'this.%(field)s >= %(value)s', | ||||
|         'lt': 'this.%(field)s < %(value)s', | ||||
|         'lte': 'this.%(field)s <= %(value)s', | ||||
|         'lte': 'this.%(field)s <= %(value)s', | ||||
|         'in': 'this.%(field)s.indexOf(%(value)s) != -1', | ||||
|         'nin': 'this.%(field)s.indexOf(%(value)s) == -1', | ||||
|         'in': '%(value)s.indexOf(this.%(field)s) != -1', | ||||
|         'nin': '%(value)s.indexOf(this.%(field)s) == -1', | ||||
|         'mod': '%(field)s %% %(value)s', | ||||
|         'all': ('%(value)s.every(function(a){' | ||||
|                 'return this.%(field)s.indexOf(a) != -1 })'), | ||||
|         'size': 'this.%(field)s.length == %(value)s', | ||||
|         'exists': 'this.%(field)s != null', | ||||
|         'regex_eq': '%(value)s.test(this.%(field)s)', | ||||
|         'regex_ne': '!%(value)s.test(this.%(field)s)', | ||||
|     } | ||||
|  | ||||
|     def __init__(self, **query): | ||||
| @@ -82,24 +96,41 @@ class Q(object): | ||||
|                 for j, (op, value) in enumerate(value.items()): | ||||
|                     # Create a custom variable name for this operator | ||||
|                     op_value_name = '%so%s' % (value_name, j) | ||||
|                     # Construct the JS that uses this op | ||||
|                     value, operation_js = self._build_op_js(op, key, value, | ||||
|                                                             op_value_name) | ||||
|                     # Update the js scope with the value for this op | ||||
|                     js_scope[op_value_name] = value | ||||
|                     # Construct the JS that uses this op | ||||
|                     operation_js = Q.OPERATORS[op.strip('$')] % { | ||||
|                         'field': key,  | ||||
|                         'value': op_value_name | ||||
|                     } | ||||
|                     js.append(operation_js) | ||||
|             else: | ||||
|                 js_scope[value_name] = value | ||||
|                 # Construct the JS for this field | ||||
|                 field_js = Q.OPERATORS[op.strip('$')] % { | ||||
|                     'field': key,  | ||||
|                     'value': value_name | ||||
|                 } | ||||
|                 value, field_js = self._build_op_js(op, key, value, value_name) | ||||
|                 js_scope[value_name] = value | ||||
|                 js.append(field_js) | ||||
|         return ' && '.join(js) | ||||
|  | ||||
|     def _build_op_js(self, op, key, value, value_name): | ||||
|         """Substitute the values in to the correct chunk of Javascript. | ||||
|         """ | ||||
|         if isinstance(value, RE_TYPE): | ||||
|             # Regexes are handled specially | ||||
|             if op.strip('$') == 'ne': | ||||
|                 op_js = Q.OPERATORS['regex_ne'] | ||||
|             else: | ||||
|                 op_js = Q.OPERATORS['regex_eq'] | ||||
|         else: | ||||
|             op_js = Q.OPERATORS[op.strip('$')] | ||||
|  | ||||
|         # Comparing two ObjectIds in Javascript doesn't work.. | ||||
|         if isinstance(value, pymongo.objectid.ObjectId): | ||||
|             value = str(value) | ||||
|  | ||||
|         # Perform the substitution | ||||
|         operation_js = op_js % { | ||||
|             'field': key,  | ||||
|             'value': value_name | ||||
|         } | ||||
|         return value, operation_js | ||||
|  | ||||
| class QuerySet(object): | ||||
|     """A set of results returned from a query. Wraps a MongoDB cursor, | ||||
| @@ -111,13 +142,17 @@ class QuerySet(object): | ||||
|         self._collection_obj = collection | ||||
|         self._accessed_collection = False | ||||
|         self._query = {} | ||||
|         self._where_clauses = [] | ||||
|         self._where_clause = None | ||||
|         self._loaded_fields = [] | ||||
|         self._ordering = [] | ||||
|          | ||||
|         # If inheritance is allowed, only return instances and instances of | ||||
|         # subclasses of the class being used | ||||
|         if document._meta.get('allow_inheritance'): | ||||
|             self._query = {'_types': self._document._class_name} | ||||
|         self._cursor_obj = None | ||||
|         self._limit = None | ||||
|         self._skip = None | ||||
|  | ||||
|     def ensure_index(self, key_or_list): | ||||
|         """Ensure that the given indexes are in place. | ||||
| @@ -126,14 +161,19 @@ class QuerySet(object): | ||||
|             construct a multi-field index); keys may be prefixed with a **+** | ||||
|             or a **-** to determine the index ordering | ||||
|         """ | ||||
|         index_list = QuerySet._build_index_spec(self._document, key_or_list) | ||||
|         self._collection.ensure_index(index_list) | ||||
|         return self | ||||
|  | ||||
|     @classmethod | ||||
|     def _build_index_spec(cls, doc_cls, key_or_list): | ||||
|         """Build a PyMongo index spec from a MongoEngine index spec. | ||||
|         """ | ||||
|         if isinstance(key_or_list, basestring): | ||||
|             key_or_list = [key_or_list] | ||||
|  | ||||
|         index_list = [] | ||||
|         # If _types is being used, prepend it to every specified index | ||||
|         if self._document._meta.get('allow_inheritance'): | ||||
|             index_list.append(('_types', 1)) | ||||
|  | ||||
|         use_types = doc_cls._meta.get('allow_inheritance', True) | ||||
|         for key in key_or_list: | ||||
|             # Get direction from + or - | ||||
|             direction = pymongo.ASCENDING | ||||
| @@ -141,25 +181,46 @@ class QuerySet(object): | ||||
|                 direction = pymongo.DESCENDING | ||||
|             if key.startswith(("+", "-")): | ||||
|                     key = key[1:] | ||||
|             # Use real field name | ||||
|             key = QuerySet._translate_field_name(self._document, key) | ||||
|  | ||||
|             # Use real field name, do it manually because we need field | ||||
|             # objects for the next part (list field checking) | ||||
|             parts = key.split('.') | ||||
|             fields = QuerySet._lookup_field(doc_cls, parts) | ||||
|             parts = [field.db_field for field in fields] | ||||
|             key = '.'.join(parts) | ||||
|             index_list.append((key, direction)) | ||||
|         self._collection.ensure_index(index_list) | ||||
|         return self | ||||
|  | ||||
|     def __call__(self, *q_objs, **query): | ||||
|             # 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 | ||||
|  | ||||
|         # If _types is being used, prepend it to every specified index | ||||
|         if doc_cls._meta.get('allow_inheritance') and use_types: | ||||
|             index_list.insert(0, ('_types', 1)) | ||||
|  | ||||
|         return index_list | ||||
|  | ||||
|     def __call__(self, q_obj=None, **query): | ||||
|         """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_obj: a :class:`~mongoengine.queryset.Q` object to be used in | ||||
|             the query; the :class:`~mongoengine.queryset.QuerySet` is filtered | ||||
|             multiple times with different :class:`~mongoengine.queryset.Q` | ||||
|             objects, only the last one will be used | ||||
|         :param query: Django-style query keyword arguments | ||||
|         """ | ||||
|         for q in q_objs: | ||||
|             self._where_clauses.append(q.as_js(self._document)) | ||||
|         if q_obj: | ||||
|             self._where_clause = q_obj.as_js(self._document) | ||||
|         query = QuerySet._transform_query(_doc_cls=self._document, **query) | ||||
|         self._query.update(query) | ||||
|         return self | ||||
|  | ||||
|     def filter(self, *q_objs, **query): | ||||
|         """An alias of :meth:`~mongoengine.queryset.QuerySet.__call__` | ||||
|         """ | ||||
|         return self.__call__(*q_objs, **query) | ||||
|  | ||||
|     @property | ||||
|     def _collection(self): | ||||
|         """Property that returns the collection object. This allows us to | ||||
| @@ -171,7 +232,8 @@ class QuerySet(object): | ||||
|             # Ensure document-defined indexes are created | ||||
|             if self._document._meta['indexes']: | ||||
|                 for key_or_list in self._document._meta['indexes']: | ||||
|                     self.ensure_index(key_or_list) | ||||
|                     #self.ensure_index(key_or_list) | ||||
|                     self._collection.ensure_index(key_or_list) | ||||
|  | ||||
|             # Ensure indexes created by uniqueness constraints | ||||
|             for index in self._document._meta['unique_indexes']: | ||||
| @@ -184,11 +246,15 @@ class QuerySet(object): | ||||
|  | ||||
|     @property | ||||
|     def _cursor(self): | ||||
|         if not self._cursor_obj: | ||||
|             self._cursor_obj = self._collection.find(self._query) | ||||
|         if self._cursor_obj is None: | ||||
|             cursor_args = {} | ||||
|             if self._loaded_fields: | ||||
|                 cursor_args = {'fields': self._loaded_fields} | ||||
|             self._cursor_obj = self._collection.find(self._query,  | ||||
|                                                      **cursor_args) | ||||
|             # Apply where clauses to cursor | ||||
|             for js in self._where_clauses: | ||||
|                 self._cursor_obj.where(js) | ||||
|             if self._where_clause: | ||||
|                 self._cursor_obj.where(self._where_clause) | ||||
|  | ||||
|             # apply default ordering | ||||
|             if self._document._meta['ordering']: | ||||
| @@ -223,38 +289,45 @@ class QuerySet(object): | ||||
|         """Translate a field attribute name to a database field name. | ||||
|         """ | ||||
|         parts = field.split(sep) | ||||
|         parts = [f.name for f in QuerySet._lookup_field(doc_cls, parts)] | ||||
|         parts = [f.db_field for f in QuerySet._lookup_field(doc_cls, parts)] | ||||
|         return '.'.join(parts) | ||||
|  | ||||
|     @classmethod | ||||
|     def _transform_query(cls, _doc_cls=None, **query): | ||||
|         """Transform a query from Django-style format to Mongo format. | ||||
|         """ | ||||
|         operators = ['neq', 'gt', 'gte', 'lt', 'lte', 'in', 'nin', 'mod', | ||||
|         operators = ['ne', 'gt', 'gte', 'lt', 'lte', 'in', 'nin', 'mod', | ||||
|                      'all', 'size', 'exists'] | ||||
|         match_operators = ['contains', 'icontains', 'startswith',  | ||||
|                            'istartswith', 'endswith', 'iendswith'] | ||||
|  | ||||
|         mongo_query = {} | ||||
|         for key, value in query.items(): | ||||
|             parts = key.split('__') | ||||
|             # Check for an operator and transform to mongo-style if there is | ||||
|             op = None | ||||
|             if parts[-1] in operators: | ||||
|             if parts[-1] in operators + match_operators: | ||||
|                 op = parts.pop() | ||||
|  | ||||
|             if _doc_cls: | ||||
|                 # Switch field names to proper names [set in Field(name='foo')] | ||||
|                 fields = QuerySet._lookup_field(_doc_cls, parts) | ||||
|                 parts = [field.name for field in fields] | ||||
|                 parts = [field.db_field for field in fields] | ||||
|  | ||||
|                 # Convert value to proper value | ||||
|                 field = fields[-1] | ||||
|                 if op in (None, 'neq', 'gt', 'gte', 'lt', 'lte'): | ||||
|                     value = field.prepare_query_value(value) | ||||
|                 singular_ops = [None, 'ne', 'gt', 'gte', 'lt', 'lte'] | ||||
|                 singular_ops += match_operators | ||||
|                 if op in singular_ops: | ||||
|                     value = field.prepare_query_value(op, value) | ||||
|                 elif op in ('in', 'nin', 'all'): | ||||
|                     # 'in', 'nin' and 'all' require a list of values | ||||
|                     value = [field.prepare_query_value(v) for v in value] | ||||
|                     value = [field.prepare_query_value(op, v) for v in value] | ||||
|  | ||||
|             if op: | ||||
|                 if field.__class__.__name__ == 'GenericReferenceField': | ||||
|                     parts.append('_ref') | ||||
|  | ||||
|             if op and op not in match_operators: | ||||
|                 value = {'$' + op: value} | ||||
|  | ||||
|             key = '.'.join(parts) | ||||
| @@ -265,6 +338,50 @@ class QuerySet(object): | ||||
|  | ||||
|         return mongo_query | ||||
|  | ||||
|     def get(self, *q_objs, **query): | ||||
|         """Retrieve the the matching object raising | ||||
|         :class:`~mongoengine.queryset.MultipleObjectsReturned` or | ||||
|         :class:`~mongoengine.queryset.DoesNotExist` exceptions if multiple or | ||||
|         no results are found. | ||||
|  | ||||
|         .. versionadded:: 0.3 | ||||
|         """ | ||||
|         self.__call__(*q_objs, **query) | ||||
|         count = self.count() | ||||
|         if count == 1: | ||||
|             return self[0] | ||||
|         elif count > 1: | ||||
|             message = u'%d items returned, instead of 1' % count | ||||
|             raise MultipleObjectsReturned(message) | ||||
|         else: | ||||
|             raise DoesNotExist('Document not found') | ||||
|  | ||||
|     def get_or_create(self, *q_objs, **query): | ||||
|         """Retreive unique object or create, if it doesn't exist. Raises | ||||
|         :class:`~mongoengine.queryset.MultipleObjectsReturned` if multiple | ||||
|         results are found. A new document will be created if the document | ||||
|         doesn't exists; a dictionary of default values for the new document | ||||
|         may be provided as a keyword argument called :attr:`defaults`. | ||||
|  | ||||
|         .. versionadded:: 0.3 | ||||
|         """ | ||||
|         defaults = query.get('defaults', {}) | ||||
|         if 'defaults' in query: | ||||
|             del query['defaults'] | ||||
|  | ||||
|         self.__call__(*q_objs, **query) | ||||
|         count = self.count() | ||||
|         if count == 0: | ||||
|             query.update(defaults) | ||||
|             doc = self._document(**query) | ||||
|             doc.save() | ||||
|             return doc | ||||
|         elif count == 1: | ||||
|             return self.first() | ||||
|         else: | ||||
|             message = u'%d items returned, instead of 1' % count | ||||
|             raise MultipleObjectsReturned(message) | ||||
|  | ||||
|     def first(self): | ||||
|         """Retrieve the first object matching the query. | ||||
|         """ | ||||
| @@ -287,26 +404,138 @@ class QuerySet(object): | ||||
|             result = self._document._from_son(result) | ||||
|         return result | ||||
|  | ||||
|     def in_bulk(self, object_ids): | ||||
|         """Retrieve a set of documents by their ids. | ||||
|          | ||||
|         :param object_ids: a list or tuple of ``ObjectId``\ s | ||||
|         :rtype: dict of ObjectIds as keys and collection-specific | ||||
|                 Document subclasses as values. | ||||
|  | ||||
|         .. versionadded:: 0.3 | ||||
|         """ | ||||
|         doc_map = {} | ||||
|  | ||||
|         docs = self._collection.find({'_id': {'$in': object_ids}}) | ||||
|         for doc in docs: | ||||
|             doc_map[doc['_id']] = self._document._from_son(doc) | ||||
|   | ||||
|         return doc_map | ||||
|  | ||||
|     def next(self): | ||||
|         """Wrap the result in a :class:`~mongoengine.Document` object. | ||||
|         """ | ||||
|         return self._document._from_son(self._cursor.next()) | ||||
|         try: | ||||
|             if self._limit == 0: | ||||
|                 raise StopIteration | ||||
|             return self._document._from_son(self._cursor.next()) | ||||
|         except StopIteration, e: | ||||
|             self.rewind() | ||||
|             raise e | ||||
|  | ||||
|     def rewind(self): | ||||
|         """Rewind the cursor to its unevaluated state. | ||||
|  | ||||
|         .. versionadded:: 0.3 | ||||
|         """ | ||||
|         self._cursor.rewind() | ||||
|  | ||||
|     def count(self): | ||||
|         """Count the selected elements in the query. | ||||
|         """ | ||||
|         return self._cursor.count() | ||||
|         if self._limit == 0: | ||||
|             return 0 | ||||
|         return self._cursor.count(with_limit_and_skip=True) | ||||
|  | ||||
|     def __len__(self): | ||||
|         return self.count() | ||||
|  | ||||
|     def map_reduce(self, map_f, reduce_f, finalize_f=None, limit=None, | ||||
|                    scope=None, keep_temp=False): | ||||
|         """Perform a map/reduce query using the current query spec | ||||
|         and ordering. While ``map_reduce`` respects ``QuerySet`` chaining, | ||||
|         it must be the last call made, as it does not return a maleable | ||||
|         ``QuerySet``. | ||||
|  | ||||
|         See the :meth:`~mongoengine.tests.QuerySetTest.test_map_reduce` | ||||
|         and :meth:`~mongoengine.tests.QuerySetTest.test_map_advanced` | ||||
|         tests in ``tests.queryset.QuerySetTest`` for usage examples. | ||||
|  | ||||
|         :param map_f: map function, as :class:`~pymongo.code.Code` or string | ||||
|         :param reduce_f: reduce function, as | ||||
|                          :class:`~pymongo.code.Code` or string | ||||
|         :param finalize_f: finalize function, an optional function that | ||||
|                            performs any post-reduction processing. | ||||
|         :param scope: values to insert into map/reduce global scope. Optional. | ||||
|         :param limit: number of objects from current query to provide | ||||
|                       to map/reduce method | ||||
|         :param keep_temp: keep temporary table (boolean, default ``True``) | ||||
|  | ||||
|         Returns an iterator yielding | ||||
|         :class:`~mongoengine.document.MapReduceDocument`. | ||||
|  | ||||
|         .. note:: Map/Reduce requires server version **>= 1.1.1**. The PyMongo | ||||
|            :meth:`~pymongo.collection.Collection.map_reduce` helper requires | ||||
|            PyMongo version **>= 1.2**. | ||||
|  | ||||
|         .. versionadded:: 0.3 | ||||
|         """ | ||||
|         from document import MapReduceDocument | ||||
|  | ||||
|         if not hasattr(self._collection, "map_reduce"): | ||||
|             raise NotImplementedError("Requires MongoDB >= 1.1.1") | ||||
|  | ||||
|         map_f_scope = {} | ||||
|         if isinstance(map_f, pymongo.code.Code): | ||||
|             map_f_scope = map_f.scope | ||||
|             map_f = str(map_f) | ||||
|         map_f = pymongo.code.Code(self._sub_js_fields(map_f), map_f_scope) | ||||
|  | ||||
|         reduce_f_scope = {} | ||||
|         if isinstance(reduce_f, pymongo.code.Code): | ||||
|             reduce_f_scope = reduce_f.scope | ||||
|             reduce_f = str(reduce_f) | ||||
|         reduce_f_code = self._sub_js_fields(reduce_f) | ||||
|         reduce_f = pymongo.code.Code(reduce_f_code, reduce_f_scope) | ||||
|  | ||||
|         mr_args = {'query': self._query, 'keeptemp': keep_temp} | ||||
|  | ||||
|         if finalize_f: | ||||
|             finalize_f_scope = {} | ||||
|             if isinstance(finalize_f, pymongo.code.Code): | ||||
|                 finalize_f_scope = finalize_f.scope | ||||
|                 finalize_f = str(finalize_f) | ||||
|             finalize_f_code = self._sub_js_fields(finalize_f) | ||||
|             finalize_f = pymongo.code.Code(finalize_f_code, finalize_f_scope) | ||||
|             mr_args['finalize'] = finalize_f | ||||
|  | ||||
|         if scope: | ||||
|             mr_args['scope'] = scope | ||||
|  | ||||
|         if limit: | ||||
|             mr_args['limit'] = limit | ||||
|  | ||||
|         results = self._collection.map_reduce(map_f, reduce_f, **mr_args) | ||||
|         results = results.find() | ||||
|  | ||||
|         if self._ordering: | ||||
|             results = results.sort(self._ordering) | ||||
|  | ||||
|         for doc in results: | ||||
|             yield MapReduceDocument(self._document, self._collection, | ||||
|                                     doc['_id'], doc['value']) | ||||
|  | ||||
|     def limit(self, n): | ||||
|         """Limit the number of returned documents to `n`. This may also be | ||||
|         achieved using array-slicing syntax (e.g. ``User.objects[:5]``). | ||||
|  | ||||
|         :param n: the maximum number of objects to return | ||||
|         """ | ||||
|         self._cursor.limit(n) | ||||
|         if n == 0: | ||||
|             self._cursor.limit(1) | ||||
|         else: | ||||
|             self._cursor.limit(n) | ||||
|         self._limit = n | ||||
|  | ||||
|         # Return self to allow chaining | ||||
|         return self | ||||
|  | ||||
| @@ -317,6 +546,7 @@ class QuerySet(object): | ||||
|         :param n: the number of objects to skip before returning results | ||||
|         """ | ||||
|         self._cursor.skip(n) | ||||
|         self._skip = n | ||||
|         return self | ||||
|  | ||||
|     def __getitem__(self, key): | ||||
| @@ -324,13 +554,48 @@ class QuerySet(object): | ||||
|         """ | ||||
|         # Slice provided | ||||
|         if isinstance(key, slice): | ||||
|             self._cursor_obj = self._cursor[key] | ||||
|             try: | ||||
|                 self._cursor_obj = self._cursor[key] | ||||
|                 self._skip, self._limit = key.start, key.stop | ||||
|             except IndexError, err: | ||||
|                 # PyMongo raises an error if key.start == key.stop, catch it, | ||||
|                 # bin it, kill it.  | ||||
|                 start = key.start or 0 | ||||
|                 if start >= 0 and key.stop >= 0 and key.step is None: | ||||
|                     if start == key.stop: | ||||
|                         self.limit(0) | ||||
|                         self._skip, self._limit = key.start, key.stop - start | ||||
|                         return self | ||||
|                 raise err | ||||
|             # Allow further QuerySet modifications to be performed | ||||
|             return self | ||||
|         # Integer index provided | ||||
|         elif isinstance(key, int): | ||||
|             return self._document._from_son(self._cursor[key]) | ||||
|  | ||||
|     def only(self, *fields): | ||||
|         """Load only a subset of this document's fields. :: | ||||
|          | ||||
|             post = BlogPost.objects(...).only("title") | ||||
|          | ||||
|         :param fields: fields to include | ||||
|  | ||||
|         .. versionadded:: 0.3 | ||||
|         """ | ||||
|         self._loaded_fields = [] | ||||
|         for field in fields: | ||||
|             if '.' in field: | ||||
|                 raise InvalidQueryError('Subfields cannot be used as ' | ||||
|                                         'arguments to QuerySet.only') | ||||
|             # Translate field name | ||||
|             field = QuerySet._lookup_field(self._document, field)[-1].db_field | ||||
|             self._loaded_fields.append(field) | ||||
|  | ||||
|         # _cls is needed for polymorphism | ||||
|         if self._document._meta.get('allow_inheritance'): | ||||
|             self._loaded_fields += ['_cls'] | ||||
|         return self | ||||
|  | ||||
|     def order_by(self, *keys): | ||||
|         """Order the :class:`~mongoengine.queryset.QuerySet` by the keys. The | ||||
|         order may be specified by prepending each of the keys by a + or a -. | ||||
| @@ -348,6 +613,7 @@ class QuerySet(object): | ||||
|                 key = key[1:] | ||||
|             key_list.append((key, direction)) | ||||
|  | ||||
|         self._ordering = key_list | ||||
|         self._cursor.sort(key_list) | ||||
|         return self | ||||
|  | ||||
| @@ -398,14 +664,14 @@ class QuerySet(object): | ||||
|             if _doc_cls: | ||||
|                 # Switch field names to proper names [set in Field(name='foo')] | ||||
|                 fields = QuerySet._lookup_field(_doc_cls, parts) | ||||
|                 parts = [field.name for field in fields] | ||||
|                 parts = [field.db_field for field in fields] | ||||
|  | ||||
|                 # Convert value to proper value | ||||
|                 field = fields[-1] | ||||
|                 if op in (None, 'set', 'unset', 'push', 'pull'): | ||||
|                     value = field.prepare_query_value(value) | ||||
|                     value = field.prepare_query_value(op, value) | ||||
|                 elif op in ('pushAll', 'pullAll'): | ||||
|                     value = [field.prepare_query_value(v) for v in value] | ||||
|                     value = [field.prepare_query_value(op, v) for v in value] | ||||
|  | ||||
|             key = '.'.join(parts) | ||||
|  | ||||
| @@ -420,7 +686,7 @@ class QuerySet(object): | ||||
|  | ||||
|         return mongo_update | ||||
|  | ||||
|     def update(self, safe_update=True, **update): | ||||
|     def update(self, safe_update=True, upsert=False, **update): | ||||
|         """Perform an atomic update on the fields matched by the query. | ||||
|  | ||||
|         :param safe: check if the operation succeeded before returning | ||||
| @@ -434,13 +700,14 @@ class QuerySet(object): | ||||
|         update = QuerySet._transform_update(self._document, **update) | ||||
|         try: | ||||
|             self._collection.update(self._query, update, safe=safe_update,  | ||||
|                                     multi=True) | ||||
|                                     upsert=upsert, multi=True) | ||||
|         except pymongo.errors.OperationFailure, err: | ||||
|             if str(err) == 'multi not coded yet': | ||||
|                 raise OperationError('update() method requires MongoDB 1.1.3+') | ||||
|             raise OperationError('Update failed (%s)' % str(err)) | ||||
|             if unicode(err) == u'multi not coded yet': | ||||
|                 message = u'update() method requires MongoDB 1.1.3+' | ||||
|                 raise OperationError(message) | ||||
|             raise OperationError(u'Update failed (%s)' % unicode(err)) | ||||
|  | ||||
|     def update_one(self, safe_update=True, **update): | ||||
|     def update_one(self, safe_update=True, upsert=False, **update): | ||||
|         """Perform an atomic update on first field matched by the query. | ||||
|  | ||||
|         :param safe: check if the operation succeeded before returning | ||||
| @@ -454,7 +721,7 @@ class QuerySet(object): | ||||
|             # as the default may change to 'True' | ||||
|             if pymongo.version >= '1.1.1': | ||||
|                 self._collection.update(self._query, update, safe=safe_update,  | ||||
|                                         multi=False) | ||||
|                                         upsert=upsert, multi=False) | ||||
|             else: | ||||
|                 # Older versions of PyMongo don't support 'multi' | ||||
|                 self._collection.update(self._query, update, safe=safe_update) | ||||
| @@ -464,6 +731,21 @@ class QuerySet(object): | ||||
|     def __iter__(self): | ||||
|         return self | ||||
|  | ||||
|     def _sub_js_fields(self, code): | ||||
|         """When fields are specified with [~fieldname] syntax, where  | ||||
|         *fieldname* is the Python name of a field, *fieldname* will be  | ||||
|         substituted for the MongoDB name of the field (specified using the | ||||
|         :attr:`name` keyword argument in a field's constructor). | ||||
|         """ | ||||
|         def field_sub(match): | ||||
|             # Extract just the field name, and look up the field objects | ||||
|             field_name = match.group(1).split('.') | ||||
|             fields = QuerySet._lookup_field(self._document, field_name) | ||||
|             # Substitute the correct name for the field into the javascript | ||||
|             return '["%s"]' % fields[-1].db_field | ||||
|  | ||||
|         return re.sub('\[\s*~([A-z_][A-z_0-9.]+?)\s*\]', field_sub, code) | ||||
|  | ||||
|     def exec_js(self, code, *fields, **options): | ||||
|         """Execute a Javascript function on the server. A list of fields may be | ||||
|         provided, which will be translated to their correct names and supplied | ||||
| @@ -473,20 +755,35 @@ class QuerySet(object): | ||||
|         current query; and ``options``, which is an object containing any | ||||
|         options specified as keyword arguments. | ||||
|  | ||||
|         As fields in MongoEngine may use different names in the database (set | ||||
|         using the :attr:`db_field` keyword argument to a :class:`Field`  | ||||
|         constructor), a mechanism exists for replacing MongoEngine field names | ||||
|         with the database field names in Javascript code. When accessing a  | ||||
|         field, use square-bracket notation, and prefix the MongoEngine field | ||||
|         name with a tilde (~). | ||||
|  | ||||
|         :param code: a string of Javascript code to execute | ||||
|         :param fields: fields that you will be using in your function, which | ||||
|             will be passed in to your function as arguments | ||||
|         :param options: options that you want available to the function | ||||
|             (accessed in Javascript through the ``options`` object) | ||||
|         """ | ||||
|         code = self._sub_js_fields(code) | ||||
|  | ||||
|         fields = [QuerySet._translate_field_name(self._document, f) | ||||
|                   for f in fields] | ||||
|         collection = self._document._meta['collection'] | ||||
|  | ||||
|         scope = { | ||||
|             'collection': collection, | ||||
|             'query': self._query, | ||||
|             'options': options or {}, | ||||
|         } | ||||
|  | ||||
|         query = self._query | ||||
|         if self._where_clause: | ||||
|             query['$where'] = self._where_clause | ||||
|  | ||||
|         scope['query'] = query | ||||
|         code = pymongo.code.Code(code, scope=scope) | ||||
|  | ||||
|         db = _get_db() | ||||
| @@ -563,11 +860,15 @@ class QuerySet(object): | ||||
|         return self.exec_js(freq_func, list_field, normalize=normalize) | ||||
|  | ||||
|     def __repr__(self): | ||||
|         data = list(self[:REPR_OUTPUT_SIZE + 1]) | ||||
|         limit = REPR_OUTPUT_SIZE + 1 | ||||
|         if self._limit is not None and self._limit < limit: | ||||
|             limit = self._limit | ||||
|         data = list(self[self._skip:limit]) | ||||
|         if len(data) > REPR_OUTPUT_SIZE: | ||||
|             data[-1] = "...(remaining elements truncated)..." | ||||
|         return repr(data) | ||||
|  | ||||
|  | ||||
| class InvalidCollectionError(Exception): | ||||
|     pass | ||||
|  | ||||
| @@ -618,14 +919,23 @@ class QuerySetManager(object): | ||||
|         # owner is the document that contains the QuerySetManager | ||||
|         queryset = QuerySet(owner, self._collection) | ||||
|         if self._manager_func: | ||||
|             queryset = self._manager_func(queryset) | ||||
|             if self._manager_func.func_code.co_argcount == 1: | ||||
|                 queryset = self._manager_func(queryset) | ||||
|             else: | ||||
|                 queryset = self._manager_func(owner, queryset) | ||||
|         return queryset | ||||
|  | ||||
|  | ||||
| def queryset_manager(func): | ||||
|     """Decorator that allows you to define custom QuerySet managers on | ||||
|     :class:`~mongoengine.Document` classes. The manager must be a function that | ||||
|     accepts a :class:`~mongoengine.queryset.QuerySet` as its only argument, and | ||||
|     returns a :class:`~mongoengine.queryset.QuerySet`, probably the same one  | ||||
|     but modified in some way. | ||||
|     accepts a :class:`~mongoengine.Document` class as its first argument, and a | ||||
|     :class:`~mongoengine.queryset.QuerySet` as its second argument. The method | ||||
|     function should return a :class:`~mongoengine.queryset.QuerySet`, probably | ||||
|     the same one that was passed in, but modified in some way. | ||||
|     """ | ||||
|     if func.func_code.co_argcount == 1: | ||||
|         import warnings | ||||
|         msg = 'Methods decorated with queryset_manager should take 2 arguments' | ||||
|         warnings.warn(msg, DeprecationWarning) | ||||
|     return QuerySetManager(func) | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import unittest | ||||
| import datetime | ||||
| from datetime import datetime | ||||
| import pymongo | ||||
|  | ||||
| from mongoengine import * | ||||
| @@ -157,6 +157,20 @@ class DocumentTest(unittest.TestCase): | ||||
|                 meta = {'allow_inheritance': False} | ||||
|         self.assertRaises(ValueError, create_employee_class) | ||||
|          | ||||
|         # Test the same for embedded documents | ||||
|         class Comment(EmbeddedDocument): | ||||
|             content = StringField() | ||||
|             meta = {'allow_inheritance': False} | ||||
|  | ||||
|         def create_special_comment(): | ||||
|             class SpecialComment(Comment): | ||||
|                 pass | ||||
|         self.assertRaises(ValueError, create_special_comment) | ||||
|  | ||||
|         comment = Comment(content='test') | ||||
|         self.assertFalse('_cls' in comment.to_mongo()) | ||||
|         self.assertFalse('_types' in comment.to_mongo()) | ||||
|  | ||||
|     def test_collection_name(self): | ||||
|         """Ensure that a collection with a specified name may be used. | ||||
|         """ | ||||
| @@ -185,7 +199,7 @@ class DocumentTest(unittest.TestCase): | ||||
|         """Ensure that capped collections work properly. | ||||
|         """ | ||||
|         class Log(Document): | ||||
|             date = DateTimeField(default=datetime.datetime.now) | ||||
|             date = DateTimeField(default=datetime.now) | ||||
|             meta = { | ||||
|                 'max_documents': 10, | ||||
|                 'max_size': 90000, | ||||
| @@ -211,7 +225,7 @@ class DocumentTest(unittest.TestCase): | ||||
|         # Check that the document cannot be redefined with different options | ||||
|         def recreate_log_document(): | ||||
|             class Log(Document): | ||||
|                 date = DateTimeField(default=datetime.datetime.now) | ||||
|                 date = DateTimeField(default=datetime.now) | ||||
|                 meta = { | ||||
|                     'max_documents': 11, | ||||
|                 } | ||||
| @@ -225,11 +239,13 @@ class DocumentTest(unittest.TestCase): | ||||
|         """Ensure that indexes are used when meta[indexes] is specified. | ||||
|         """ | ||||
|         class BlogPost(Document): | ||||
|             date = DateTimeField(name='addDate', default=datetime.datetime.now) | ||||
|             date = DateTimeField(db_field='addDate', default=datetime.now) | ||||
|             category = StringField() | ||||
|             tags = ListField(StringField()) | ||||
|             meta = { | ||||
|                 'indexes': [ | ||||
|                     '-date',  | ||||
|                     'tags', | ||||
|                     ('category', '-date') | ||||
|                 ], | ||||
|             } | ||||
| @@ -237,7 +253,8 @@ class DocumentTest(unittest.TestCase): | ||||
|         BlogPost.drop_collection() | ||||
|  | ||||
|         info = BlogPost.objects._collection.index_information() | ||||
|         self.assertEqual(len(info), 4) # _id, types, '-date', ('cat', 'date') | ||||
|         # _id, types, '-date', 'tags', ('cat', 'date') | ||||
|         self.assertEqual(len(info), 5) | ||||
|  | ||||
|         # Indexes are lazy so use list() to perform query | ||||
|         list(BlogPost.objects) | ||||
| @@ -245,6 +262,8 @@ class DocumentTest(unittest.TestCase): | ||||
|         self.assertTrue([('_types', 1), ('category', 1), ('addDate', -1)]  | ||||
|                         in info.values()) | ||||
|         self.assertTrue([('_types', 1), ('addDate', -1)] in info.values()) | ||||
|         # tags is a list field so it shouldn't have _types in the index | ||||
|         self.assertTrue([('tags', 1)] in info.values()) | ||||
|          | ||||
|         class ExtendedBlogPost(BlogPost): | ||||
|             title = StringField() | ||||
| @@ -278,7 +297,7 @@ class DocumentTest(unittest.TestCase): | ||||
|         self.assertRaises(OperationError, post2.save) | ||||
|  | ||||
|         class Date(EmbeddedDocument): | ||||
|             year = IntField(name='yr') | ||||
|             year = IntField(db_field='yr') | ||||
|  | ||||
|         class BlogPost(Document): | ||||
|             title = StringField() | ||||
| @@ -309,7 +328,7 @@ class DocumentTest(unittest.TestCase): | ||||
|  | ||||
|         User.drop_collection() | ||||
|  | ||||
|         self.assertEqual(User._fields['username'].name, '_id') | ||||
|         self.assertEqual(User._fields['username'].db_field, '_id') | ||||
|         self.assertEqual(User._meta['id_field'], 'username') | ||||
|  | ||||
|         def create_invalid_user(): | ||||
| @@ -386,7 +405,26 @@ class DocumentTest(unittest.TestCase): | ||||
|          | ||||
|         self.assertTrue('content' in Comment._fields) | ||||
|         self.assertFalse('id' in Comment._fields) | ||||
|         self.assertFalse(hasattr(Comment, '_meta')) | ||||
|         self.assertFalse('collection' in 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.now() | ||||
|         comment.validate() | ||||
|  | ||||
|     def test_save(self): | ||||
|         """Ensure that a document may be saved in the database. | ||||
| @@ -399,7 +437,7 @@ class DocumentTest(unittest.TestCase): | ||||
|         person_obj = collection.find_one({'name': 'Test User'}) | ||||
|         self.assertEqual(person_obj['name'], 'Test User') | ||||
|         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): | ||||
|         """Ensure that document may be deleted using the delete method. | ||||
|   | ||||
							
								
								
									
										295
									
								
								tests/fields.py
									
									
									
									
									
								
							
							
						
						
									
										295
									
								
								tests/fields.py
									
									
									
									
									
								
							| @@ -1,5 +1,8 @@ | ||||
| import unittest | ||||
| import datetime | ||||
| from decimal import Decimal | ||||
|  | ||||
| import pymongo | ||||
|  | ||||
| from mongoengine import * | ||||
| from mongoengine.connection import _get_db | ||||
| @@ -79,6 +82,19 @@ class FieldTest(unittest.TestCase): | ||||
|         person.name = 'Shorter name' | ||||
|         person.validate() | ||||
|  | ||||
|     def test_url_validation(self): | ||||
|         """Ensure that URLFields validate urls properly. | ||||
|         """ | ||||
|         class Link(Document): | ||||
|             url = URLField() | ||||
|  | ||||
|         link = Link() | ||||
|         link.url = 'google' | ||||
|         self.assertRaises(ValidationError, link.validate) | ||||
|  | ||||
|         link.url = 'http://www.google.com:8080' | ||||
|         link.validate() | ||||
|          | ||||
|     def test_int_validation(self): | ||||
|         """Ensure that invalid values cannot be assigned to int fields. | ||||
|         """ | ||||
| @@ -106,13 +122,33 @@ class FieldTest(unittest.TestCase): | ||||
|         person.height = 1.89 | ||||
|         person.validate() | ||||
|  | ||||
|         person.height = 2 | ||||
|         person.height = '2.0' | ||||
|         self.assertRaises(ValidationError, person.validate) | ||||
|         person.height = 0.01 | ||||
|         self.assertRaises(ValidationError, person.validate) | ||||
|         person.height = 4.0 | ||||
|         self.assertRaises(ValidationError, person.validate) | ||||
|          | ||||
|     def test_decimal_validation(self): | ||||
|         """Ensure that invalid values cannot be assigned to decimal fields. | ||||
|         """ | ||||
|         class Person(Document): | ||||
|             height = DecimalField(min_value=Decimal('0.1'),  | ||||
|                                   max_value=Decimal('3.5')) | ||||
|  | ||||
|         person = Person() | ||||
|         person.height = Decimal('1.89') | ||||
|         person.validate() | ||||
|  | ||||
|         person.height = '2.0' | ||||
|         person.validate() | ||||
|         person.height = 0.01 | ||||
|         self.assertRaises(ValidationError, person.validate) | ||||
|         person.height = Decimal('0.01') | ||||
|         self.assertRaises(ValidationError, person.validate) | ||||
|         person.height = Decimal('4.0') | ||||
|         self.assertRaises(ValidationError, person.validate) | ||||
|  | ||||
|     def test_boolean_validation(self): | ||||
|         """Ensure that invalid values cannot be assigned to boolean fields. | ||||
|         """ | ||||
| @@ -176,6 +212,28 @@ class FieldTest(unittest.TestCase): | ||||
|         post.comments = 'yay' | ||||
|         self.assertRaises(ValidationError, post.validate) | ||||
|  | ||||
|     def test_dict_validation(self): | ||||
|         """Ensure that dict types work as expected. | ||||
|         """ | ||||
|         class BlogPost(Document): | ||||
|             info = DictField() | ||||
|  | ||||
|         post = BlogPost() | ||||
|         post.info = 'my post' | ||||
|         self.assertRaises(ValidationError, post.validate) | ||||
|  | ||||
|         post.info = ['test', 'test'] | ||||
|         self.assertRaises(ValidationError, post.validate) | ||||
|  | ||||
|         post.info = {'$title': 'test'} | ||||
|         self.assertRaises(ValidationError, post.validate) | ||||
|  | ||||
|         post.info = {'the.title': 'test'} | ||||
|         self.assertRaises(ValidationError, post.validate) | ||||
|  | ||||
|         post.info = {'title': 'test'} | ||||
|         post.validate() | ||||
|  | ||||
|     def test_embedded_document_validation(self): | ||||
|         """Ensure that invalid embedded documents cannot be assigned to | ||||
|         embedded document fields. | ||||
| @@ -184,7 +242,7 @@ class FieldTest(unittest.TestCase): | ||||
|             content = StringField() | ||||
|  | ||||
|         class PersonPreferences(EmbeddedDocument): | ||||
|             food = StringField() | ||||
|             food = StringField(required=True) | ||||
|             number = IntField() | ||||
|  | ||||
|         class Person(Document): | ||||
| @@ -195,9 +253,14 @@ class FieldTest(unittest.TestCase): | ||||
|         person.preferences = 'My Preferences' | ||||
|         self.assertRaises(ValidationError, person.validate) | ||||
|  | ||||
|         # Check that only the right embedded doc works | ||||
|         person.preferences = Comment(content='Nice blog post...') | ||||
|         self.assertRaises(ValidationError, person.validate) | ||||
|  | ||||
|         # Check that the embedded doc is valid | ||||
|         person.preferences = PersonPreferences() | ||||
|         self.assertRaises(ValidationError, person.validate) | ||||
|  | ||||
|         person.preferences = PersonPreferences(food='Cheese', number=47) | ||||
|         self.assertEqual(person.preferences.food, 'Cheese') | ||||
|         person.validate() | ||||
| @@ -259,6 +322,234 @@ class FieldTest(unittest.TestCase): | ||||
|         User.drop_collection() | ||||
|         BlogPost.drop_collection() | ||||
|      | ||||
|     def test_list_item_dereference(self): | ||||
|         """Ensure that DBRef items in ListFields are dereferenced. | ||||
|         """ | ||||
|         class User(Document): | ||||
|             name = StringField() | ||||
|  | ||||
|         class Group(Document): | ||||
|             members = ListField(ReferenceField(User)) | ||||
|  | ||||
|         User.drop_collection() | ||||
|         Group.drop_collection() | ||||
|  | ||||
|         user1 = User(name='user1') | ||||
|         user1.save() | ||||
|         user2 = User(name='user2') | ||||
|         user2.save() | ||||
|  | ||||
|         group = Group(members=[user1, user2]) | ||||
|         group.save() | ||||
|  | ||||
|         group_obj = Group.objects.first() | ||||
|  | ||||
|         self.assertEqual(group_obj.members[0].name, user1.name) | ||||
|         self.assertEqual(group_obj.members[1].name, user2.name) | ||||
|  | ||||
|         User.drop_collection() | ||||
|         Group.drop_collection() | ||||
|  | ||||
|     def test_recursive_reference(self): | ||||
|         """Ensure that ReferenceFields can reference their own documents. | ||||
|         """ | ||||
|         class Employee(Document): | ||||
|             name = StringField() | ||||
|             boss = ReferenceField('self') | ||||
|  | ||||
|         bill = Employee(name='Bill Lumbergh') | ||||
|         bill.save() | ||||
|         peter = Employee(name='Peter Gibbons', boss=bill) | ||||
|         peter.save() | ||||
|  | ||||
|         peter = Employee.objects.with_id(peter.id) | ||||
|         self.assertEqual(peter.boss, bill) | ||||
|  | ||||
|     def test_undefined_reference(self): | ||||
|         """Ensure that ReferenceFields may reference undefined Documents. | ||||
|         """ | ||||
|         class Product(Document): | ||||
|             name = StringField() | ||||
|             company = ReferenceField('Company') | ||||
|  | ||||
|         class Company(Document): | ||||
|             name = StringField() | ||||
|  | ||||
|         ten_gen = Company(name='10gen') | ||||
|         ten_gen.save() | ||||
|         mongodb = Product(name='MongoDB', company=ten_gen) | ||||
|         mongodb.save() | ||||
|  | ||||
|         obj = Product.objects(company=ten_gen).first() | ||||
|         self.assertEqual(obj, mongodb) | ||||
|         self.assertEqual(obj.company, ten_gen) | ||||
|  | ||||
|     def test_reference_query_conversion(self): | ||||
|         """Ensure that ReferenceFields can be queried using objects and values | ||||
|         of the type of the primary key of the referenced object. | ||||
|         """ | ||||
|         class Member(Document): | ||||
|             user_num = IntField(primary_key=True) | ||||
|  | ||||
|         class BlogPost(Document): | ||||
|             title = StringField() | ||||
|             author = ReferenceField(Member) | ||||
|  | ||||
|         Member.drop_collection() | ||||
|         BlogPost.drop_collection() | ||||
|  | ||||
|         m1 = Member(user_num=1) | ||||
|         m1.save() | ||||
|         m2 = Member(user_num=2) | ||||
|         m2.save() | ||||
|  | ||||
|         post1 = BlogPost(title='post 1', author=m1) | ||||
|         post1.save() | ||||
|  | ||||
|         post2 = BlogPost(title='post 2', author=m2) | ||||
|         post2.save() | ||||
|  | ||||
|         post = BlogPost.objects(author=m1).first() | ||||
|         self.assertEqual(post.id, post1.id) | ||||
|  | ||||
|         post = BlogPost.objects(author=m2).first() | ||||
|         self.assertEqual(post.id, post2.id) | ||||
|  | ||||
|         Member.drop_collection() | ||||
|         BlogPost.drop_collection() | ||||
|          | ||||
|     def test_generic_reference(self): | ||||
|         """Ensure that a GenericReferenceField properly dereferences items. | ||||
|         """ | ||||
|         class Link(Document): | ||||
|             title = StringField() | ||||
|             meta = {'allow_inheritance': False} | ||||
|              | ||||
|         class Post(Document): | ||||
|             title = StringField() | ||||
|              | ||||
|         class Bookmark(Document): | ||||
|             bookmark_object = GenericReferenceField() | ||||
|              | ||||
|         Link.drop_collection() | ||||
|         Post.drop_collection() | ||||
|         Bookmark.drop_collection() | ||||
|      | ||||
|         link_1 = Link(title="Pitchfork") | ||||
|         link_1.save() | ||||
|      | ||||
|         post_1 = Post(title="Behind the Scenes of the Pavement Reunion") | ||||
|         post_1.save() | ||||
|          | ||||
|         bm = Bookmark(bookmark_object=post_1) | ||||
|         bm.save() | ||||
|          | ||||
|         bm = Bookmark.objects(bookmark_object=post_1).first() | ||||
|          | ||||
|         self.assertEqual(bm.bookmark_object, post_1) | ||||
|         self.assertTrue(isinstance(bm.bookmark_object, Post)) | ||||
|          | ||||
|         bm.bookmark_object = link_1 | ||||
|         bm.save() | ||||
|          | ||||
|         bm = Bookmark.objects(bookmark_object=link_1).first() | ||||
|          | ||||
|         self.assertEqual(bm.bookmark_object, link_1) | ||||
|         self.assertTrue(isinstance(bm.bookmark_object, Link)) | ||||
|      | ||||
|         Link.drop_collection() | ||||
|         Post.drop_collection() | ||||
|         Bookmark.drop_collection() | ||||
|  | ||||
|     def test_generic_reference_list(self): | ||||
|         """Ensure that a ListField properly dereferences generic references. | ||||
|         """ | ||||
|         class Link(Document): | ||||
|             title = StringField() | ||||
|      | ||||
|         class Post(Document): | ||||
|             title = StringField() | ||||
|      | ||||
|         class User(Document): | ||||
|             bookmarks = ListField(GenericReferenceField()) | ||||
|      | ||||
|         Link.drop_collection() | ||||
|         Post.drop_collection() | ||||
|         User.drop_collection() | ||||
|      | ||||
|         link_1 = Link(title="Pitchfork") | ||||
|         link_1.save() | ||||
|      | ||||
|         post_1 = Post(title="Behind the Scenes of the Pavement Reunion") | ||||
|         post_1.save() | ||||
|      | ||||
|         user = User(bookmarks=[post_1, link_1]) | ||||
|         user.save() | ||||
|          | ||||
|         user = User.objects(bookmarks__all=[post_1, link_1]).first() | ||||
|          | ||||
|         self.assertEqual(user.bookmarks[0], post_1) | ||||
|         self.assertEqual(user.bookmarks[1], link_1) | ||||
|          | ||||
|         Link.drop_collection() | ||||
|         Post.drop_collection() | ||||
|         User.drop_collection() | ||||
|  | ||||
|     def test_binary_fields(self): | ||||
|         """Ensure that binary fields can be stored and retrieved. | ||||
|         """ | ||||
|         class Attachment(Document): | ||||
|             content_type = StringField() | ||||
|             blob = BinaryField() | ||||
|  | ||||
|         BLOB = '\xe6\x00\xc4\xff\x07' | ||||
|         MIME_TYPE = 'application/octet-stream' | ||||
|  | ||||
|         Attachment.drop_collection() | ||||
|  | ||||
|         attachment = Attachment(content_type=MIME_TYPE, blob=BLOB) | ||||
|         attachment.save() | ||||
|  | ||||
|         attachment_1 = Attachment.objects().first() | ||||
|         self.assertEqual(MIME_TYPE, attachment_1.content_type) | ||||
|         self.assertEqual(BLOB, attachment_1.blob) | ||||
|  | ||||
|         Attachment.drop_collection() | ||||
|  | ||||
|     def test_binary_validation(self): | ||||
|         """Ensure that invalid values cannot be assigned to binary fields. | ||||
|         """ | ||||
|         class Attachment(Document): | ||||
|             blob = BinaryField() | ||||
|  | ||||
|         class AttachmentRequired(Document): | ||||
|             blob = BinaryField(required=True) | ||||
|  | ||||
|         class AttachmentSizeLimit(Document): | ||||
|             blob = BinaryField(max_bytes=4) | ||||
|  | ||||
|         Attachment.drop_collection() | ||||
|         AttachmentRequired.drop_collection() | ||||
|         AttachmentSizeLimit.drop_collection() | ||||
|  | ||||
|         attachment = Attachment() | ||||
|         attachment.validate() | ||||
|         attachment.blob = 2 | ||||
|         self.assertRaises(ValidationError, attachment.validate) | ||||
|  | ||||
|         attachment_required = AttachmentRequired() | ||||
|         self.assertRaises(ValidationError, attachment_required.validate) | ||||
|         attachment_required.blob = '\xe6\x00\xc4\xff\x07' | ||||
|         attachment_required.validate() | ||||
|  | ||||
|         attachment_size_limit = AttachmentSizeLimit(blob='\xe6\x00\xc4\xff\x07') | ||||
|         self.assertRaises(ValidationError, attachment_size_limit.validate) | ||||
|         attachment_size_limit.blob = '\xe6\x00\xc4\xff' | ||||
|         attachment_size_limit.validate() | ||||
|  | ||||
|         Attachment.drop_collection() | ||||
|         AttachmentRequired.drop_collection() | ||||
|         AttachmentSizeLimit.drop_collection() | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     unittest.main() | ||||
|   | ||||
| @@ -1,8 +1,12 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
|  | ||||
| import unittest | ||||
| import pymongo | ||||
| from datetime import datetime | ||||
| from datetime import datetime, timedelta | ||||
|  | ||||
| from mongoengine.queryset import QuerySet | ||||
| from mongoengine.queryset import (QuerySet, MultipleObjectsReturned, | ||||
|                                   DoesNotExist) | ||||
| from mongoengine import * | ||||
|  | ||||
|  | ||||
| @@ -20,7 +24,7 @@ class QuerySetTest(unittest.TestCase): | ||||
|         """Ensure that a QuerySet is correctly initialised by QuerySetManager. | ||||
|         """ | ||||
|         self.assertTrue(isinstance(self.Person.objects, QuerySet)) | ||||
|         self.assertEqual(self.Person.objects._collection.name(),  | ||||
|         self.assertEqual(self.Person.objects._collection.name, | ||||
|                          self.Person._meta['collection']) | ||||
|         self.assertTrue(isinstance(self.Person.objects._collection, | ||||
|                                    pymongo.collection.Collection)) | ||||
| @@ -58,7 +62,7 @@ class QuerySetTest(unittest.TestCase): | ||||
|         results = list(people) | ||||
|         self.assertTrue(isinstance(results[0], self.Person)) | ||||
|         self.assertTrue(isinstance(results[0].id, (pymongo.objectid.ObjectId, | ||||
|                                                     str, unicode))) | ||||
|                                                    str, unicode))) | ||||
|         self.assertEqual(results[0].name, "User A") | ||||
|         self.assertEqual(results[0].age, 20) | ||||
|         self.assertEqual(results[1].name, "User B") | ||||
| @@ -101,6 +105,9 @@ class QuerySetTest(unittest.TestCase): | ||||
|         self.assertEqual(len(people), 1) | ||||
|         self.assertEqual(people[0].name, 'User B') | ||||
|  | ||||
|         people = list(self.Person.objects[1:1]) | ||||
|         self.assertEqual(len(people), 0) | ||||
|  | ||||
|     def test_find_one(self): | ||||
|         """Ensure that a query using find_one returns a valid result. | ||||
|         """ | ||||
| @@ -135,6 +142,155 @@ class QuerySetTest(unittest.TestCase): | ||||
|         person = self.Person.objects.with_id(person1.id) | ||||
|         self.assertEqual(person.name, "User A") | ||||
|  | ||||
|     def test_find_only_one(self): | ||||
|         """Ensure that a query using ``get`` returns at most one result. | ||||
|         """ | ||||
|         # Try retrieving when no objects exists | ||||
|         self.assertRaises(DoesNotExist, self.Person.objects.get) | ||||
|  | ||||
|         person1 = self.Person(name="User A", age=20) | ||||
|         person1.save() | ||||
|         person2 = self.Person(name="User B", age=30) | ||||
|         person2.save() | ||||
|  | ||||
|         # Retrieve the first person from the database | ||||
|         self.assertRaises(MultipleObjectsReturned, self.Person.objects.get) | ||||
|  | ||||
|         # Use a query to filter the people found to just person2 | ||||
|         person = self.Person.objects.get(age=30) | ||||
|         self.assertEqual(person.name, "User B") | ||||
|  | ||||
|         person = self.Person.objects.get(age__lt=30) | ||||
|         self.assertEqual(person.name, "User A") | ||||
|  | ||||
|     def test_get_or_create(self): | ||||
|         """Ensure that ``get_or_create`` returns one result or creates a new | ||||
|         document. | ||||
|         """ | ||||
|         person1 = self.Person(name="User A", age=20) | ||||
|         person1.save() | ||||
|         person2 = self.Person(name="User B", age=30) | ||||
|         person2.save() | ||||
|  | ||||
|         # Retrieve the first person from the database | ||||
|         self.assertRaises(MultipleObjectsReturned, | ||||
|                           self.Person.objects.get_or_create) | ||||
|  | ||||
|         # Use a query to filter the people found to just person2 | ||||
|         person = self.Person.objects.get_or_create(age=30) | ||||
|         self.assertEqual(person.name, "User B") | ||||
|  | ||||
|         person = self.Person.objects.get_or_create(age__lt=30) | ||||
|         self.assertEqual(person.name, "User A") | ||||
|  | ||||
|         # Try retrieving when no objects exists - new doc should be created | ||||
|         self.Person.objects.get_or_create(age=50, defaults={'name': 'User C'}) | ||||
|  | ||||
|         person = self.Person.objects.get(age=50) | ||||
|         self.assertEqual(person.name, "User C") | ||||
|  | ||||
|     def test_repeated_iteration(self): | ||||
|         """Ensure that QuerySet rewinds itself one iteration finishes. | ||||
|         """ | ||||
|         self.Person(name='Person 1').save() | ||||
|         self.Person(name='Person 2').save() | ||||
|  | ||||
|         queryset = self.Person.objects | ||||
|         people1 = [person for person in queryset] | ||||
|         people2 = [person for person in queryset] | ||||
|  | ||||
|         self.assertEqual(people1, people2) | ||||
|  | ||||
|     def test_regex_query_shortcuts(self): | ||||
|         """Ensure that contains, startswith, endswith, etc work. | ||||
|         """ | ||||
|         person = self.Person(name='Guido van Rossum') | ||||
|         person.save() | ||||
|  | ||||
|         # Test contains | ||||
|         obj = self.Person.objects(name__contains='van').first() | ||||
|         self.assertEqual(obj, person) | ||||
|         obj = self.Person.objects(name__contains='Van').first() | ||||
|         self.assertEqual(obj, None) | ||||
|         obj = self.Person.objects(Q(name__contains='van')).first() | ||||
|         self.assertEqual(obj, person) | ||||
|         obj = self.Person.objects(Q(name__contains='Van')).first() | ||||
|         self.assertEqual(obj, None) | ||||
|  | ||||
|         # Test icontains | ||||
|         obj = self.Person.objects(name__icontains='Van').first() | ||||
|         self.assertEqual(obj, person) | ||||
|         obj = self.Person.objects(Q(name__icontains='Van')).first() | ||||
|         self.assertEqual(obj, person) | ||||
|  | ||||
|         # Test startswith | ||||
|         obj = self.Person.objects(name__startswith='Guido').first() | ||||
|         self.assertEqual(obj, person) | ||||
|         obj = self.Person.objects(name__startswith='guido').first() | ||||
|         self.assertEqual(obj, None) | ||||
|         obj = self.Person.objects(Q(name__startswith='Guido')).first() | ||||
|         self.assertEqual(obj, person) | ||||
|         obj = self.Person.objects(Q(name__startswith='guido')).first() | ||||
|         self.assertEqual(obj, None) | ||||
|  | ||||
|         # Test istartswith | ||||
|         obj = self.Person.objects(name__istartswith='guido').first() | ||||
|         self.assertEqual(obj, person) | ||||
|         obj = self.Person.objects(Q(name__istartswith='guido')).first() | ||||
|         self.assertEqual(obj, person) | ||||
|  | ||||
|         # Test endswith | ||||
|         obj = self.Person.objects(name__endswith='Rossum').first() | ||||
|         self.assertEqual(obj, person) | ||||
|         obj = self.Person.objects(name__endswith='rossuM').first() | ||||
|         self.assertEqual(obj, None) | ||||
|         obj = self.Person.objects(Q(name__endswith='Rossum')).first() | ||||
|         self.assertEqual(obj, person) | ||||
|         obj = self.Person.objects(Q(name__endswith='rossuM')).first() | ||||
|         self.assertEqual(obj, None) | ||||
|  | ||||
|         # Test iendswith | ||||
|         obj = self.Person.objects(name__iendswith='rossuM').first() | ||||
|         self.assertEqual(obj, person) | ||||
|         obj = self.Person.objects(Q(name__iendswith='rossuM')).first() | ||||
|         self.assertEqual(obj, person) | ||||
|  | ||||
|     def test_filter_chaining(self): | ||||
|         """Ensure filters can be chained together. | ||||
|         """ | ||||
|         from datetime import datetime | ||||
|  | ||||
|         class BlogPost(Document): | ||||
|             title = StringField() | ||||
|             is_published = BooleanField() | ||||
|             published_date = DateTimeField() | ||||
|  | ||||
|             @queryset_manager | ||||
|             def published(doc_cls, queryset): | ||||
|                 return queryset(is_published=True) | ||||
|  | ||||
|         blog_post_1 = BlogPost(title="Blog Post #1", | ||||
|                                is_published = True, | ||||
|                                published_date=datetime(2010, 1, 5, 0, 0 ,0)) | ||||
|         blog_post_2 = BlogPost(title="Blog Post #2", | ||||
|                                is_published = True, | ||||
|                                published_date=datetime(2010, 1, 6, 0, 0 ,0)) | ||||
|         blog_post_3 = BlogPost(title="Blog Post #3", | ||||
|                                is_published = True, | ||||
|                                published_date=datetime(2010, 1, 7, 0, 0 ,0)) | ||||
|  | ||||
|         blog_post_1.save() | ||||
|         blog_post_2.save() | ||||
|         blog_post_3.save() | ||||
|  | ||||
|         # find all published blog posts before 2010-01-07 | ||||
|         published_posts = BlogPost.published() | ||||
|         published_posts = published_posts.filter( | ||||
|             published_date__lt=datetime(2010, 1, 7, 0, 0 ,0)) | ||||
|         self.assertEqual(published_posts.count(), 2) | ||||
|  | ||||
|         BlogPost.drop_collection() | ||||
|  | ||||
|     def test_ordering(self): | ||||
|         """Ensure default ordering is applied and can be overridden. | ||||
|         """ | ||||
| @@ -170,6 +326,39 @@ class QuerySetTest(unittest.TestCase): | ||||
|  | ||||
|         BlogPost.drop_collection() | ||||
|  | ||||
|     def test_only(self): | ||||
|         """Ensure that QuerySet.only only returns the requested fields. | ||||
|         """ | ||||
|         person = self.Person(name='test', age=25) | ||||
|         person.save() | ||||
|  | ||||
|         obj = self.Person.objects.only('name').get() | ||||
|         self.assertEqual(obj.name, person.name) | ||||
|         self.assertEqual(obj.age, None) | ||||
|  | ||||
|         obj = self.Person.objects.only('age').get() | ||||
|         self.assertEqual(obj.name, None) | ||||
|         self.assertEqual(obj.age, person.age) | ||||
|  | ||||
|         obj = self.Person.objects.only('name', 'age').get() | ||||
|         self.assertEqual(obj.name, person.name) | ||||
|         self.assertEqual(obj.age, person.age) | ||||
|  | ||||
|         # Check polymorphism still works | ||||
|         class Employee(self.Person): | ||||
|             salary = IntField(db_field='wage') | ||||
|  | ||||
|         employee = Employee(name='test employee', age=40, salary=30000) | ||||
|         employee.save() | ||||
|  | ||||
|         obj = self.Person.objects(id=employee.id).only('age').get() | ||||
|         self.assertTrue(isinstance(obj, Employee)) | ||||
|  | ||||
|         # Check field names are looked up properly | ||||
|         obj = Employee.objects(id=employee.id).only('salary').get() | ||||
|         self.assertEqual(obj.salary, employee.salary) | ||||
|         self.assertEqual(obj.name, None) | ||||
|  | ||||
|     def test_find_embedded(self): | ||||
|         """Ensure that an embedded document is properly returned from a query. | ||||
|         """ | ||||
| @@ -192,7 +381,25 @@ class QuerySetTest(unittest.TestCase): | ||||
|  | ||||
|         BlogPost.drop_collection() | ||||
|  | ||||
|     def test_find_dict_item(self): | ||||
|         """Ensure that DictField items may be found. | ||||
|         """ | ||||
|         class BlogPost(Document): | ||||
|             info = DictField() | ||||
|  | ||||
|         BlogPost.drop_collection() | ||||
|  | ||||
|         post = BlogPost(info={'title': 'test'}) | ||||
|         post.save() | ||||
|  | ||||
|         post_obj = BlogPost.objects(info__title='test').first() | ||||
|         self.assertEqual(post_obj.id, post.id) | ||||
|  | ||||
|         BlogPost.drop_collection() | ||||
|  | ||||
|     def test_q(self): | ||||
|         """Ensure that Q objects may be used to query for documents. | ||||
|         """ | ||||
|         class BlogPost(Document): | ||||
|             publish_date = DateTimeField() | ||||
|             published = BooleanField() | ||||
| @@ -217,6 +424,11 @@ class QuerySetTest(unittest.TestCase): | ||||
|         post6 = BlogPost(published=False) | ||||
|         post6.save() | ||||
|  | ||||
|         # Check ObjectId lookup works | ||||
|         obj = BlogPost.objects(id=post1.id).first() | ||||
|         self.assertEqual(obj, post1) | ||||
|  | ||||
|         # Check Q object combination | ||||
|         date = datetime(2010, 1, 10) | ||||
|         q = BlogPost.objects(Q(publish_date__lte=date) | Q(published=True)) | ||||
|         posts = [post.id for post in q] | ||||
| @@ -228,6 +440,132 @@ class QuerySetTest(unittest.TestCase): | ||||
|  | ||||
|         BlogPost.drop_collection() | ||||
|  | ||||
|         # Check the 'in' operator | ||||
|         self.Person(name='user1', age=20).save() | ||||
|         self.Person(name='user2', age=20).save() | ||||
|         self.Person(name='user3', age=30).save() | ||||
|         self.Person(name='user4', age=40).save() | ||||
|  | ||||
|         self.assertEqual(len(self.Person.objects(Q(age__in=[20]))), 2) | ||||
|         self.assertEqual(len(self.Person.objects(Q(age__in=[20, 30]))), 3) | ||||
|  | ||||
|     def test_q_regex(self): | ||||
|         """Ensure that Q objects can be queried using regexes. | ||||
|         """ | ||||
|         person = self.Person(name='Guido van Rossum') | ||||
|         person.save() | ||||
|  | ||||
|         import re | ||||
|         obj = self.Person.objects(Q(name=re.compile('^Gui'))).first() | ||||
|         self.assertEqual(obj, person) | ||||
|         obj = self.Person.objects(Q(name=re.compile('^gui'))).first() | ||||
|         self.assertEqual(obj, None) | ||||
|  | ||||
|         obj = self.Person.objects(Q(name=re.compile('^gui', re.I))).first() | ||||
|         self.assertEqual(obj, person) | ||||
|  | ||||
|         obj = self.Person.objects(Q(name__ne=re.compile('^bob'))).first() | ||||
|         self.assertEqual(obj, person) | ||||
|         obj = self.Person.objects(Q(name__ne=re.compile('^Gui'))).first() | ||||
|         self.assertEqual(obj, None) | ||||
|  | ||||
|     def test_exec_js_query(self): | ||||
|         """Ensure that queries are properly formed for use in exec_js. | ||||
|         """ | ||||
|         class BlogPost(Document): | ||||
|             hits = IntField() | ||||
|             published = BooleanField() | ||||
|  | ||||
|         BlogPost.drop_collection() | ||||
|  | ||||
|         post1 = BlogPost(hits=1, published=False) | ||||
|         post1.save() | ||||
|  | ||||
|         post2 = BlogPost(hits=1, published=True) | ||||
|         post2.save() | ||||
|  | ||||
|         post3 = BlogPost(hits=1, published=True) | ||||
|         post3.save() | ||||
|  | ||||
|         js_func = """ | ||||
|             function(hitsField) { | ||||
|                 var count = 0; | ||||
|                 db[collection].find(query).forEach(function(doc) { | ||||
|                     count += doc[hitsField]; | ||||
|                 }); | ||||
|                 return count; | ||||
|             } | ||||
|         """ | ||||
|  | ||||
|         # Ensure that normal queries work | ||||
|         c = BlogPost.objects(published=True).exec_js(js_func, 'hits') | ||||
|         self.assertEqual(c, 2) | ||||
|  | ||||
|         c = BlogPost.objects(published=False).exec_js(js_func, 'hits') | ||||
|         self.assertEqual(c, 1) | ||||
|  | ||||
|         # Ensure that Q object queries work | ||||
|         c = BlogPost.objects(Q(published=True)).exec_js(js_func, 'hits') | ||||
|         self.assertEqual(c, 2) | ||||
|  | ||||
|         c = BlogPost.objects(Q(published=False)).exec_js(js_func, 'hits') | ||||
|         self.assertEqual(c, 1) | ||||
|  | ||||
|         BlogPost.drop_collection() | ||||
|  | ||||
|     def test_exec_js_field_sub(self): | ||||
|         """Ensure that field substitutions occur properly in exec_js functions. | ||||
|         """ | ||||
|         class Comment(EmbeddedDocument): | ||||
|             content = StringField(db_field='body') | ||||
|  | ||||
|         class BlogPost(Document): | ||||
|             name = StringField(db_field='doc-name') | ||||
|             comments = ListField(EmbeddedDocumentField(Comment),  | ||||
|                                  db_field='cmnts') | ||||
|  | ||||
|         BlogPost.drop_collection() | ||||
|  | ||||
|         comments1 = [Comment(content='cool'), Comment(content='yay')] | ||||
|         post1 = BlogPost(name='post1', comments=comments1) | ||||
|         post1.save() | ||||
|  | ||||
|         comments2 = [Comment(content='nice stuff')] | ||||
|         post2 = BlogPost(name='post2', comments=comments2) | ||||
|         post2.save() | ||||
|  | ||||
|         code = """ | ||||
|         function getComments() { | ||||
|             var comments = []; | ||||
|             db[collection].find(query).forEach(function(doc) { | ||||
|                 var docComments = doc[~comments]; | ||||
|                 for (var i = 0; i < docComments.length; i++) { | ||||
|                     comments.push({ | ||||
|                         'document': doc[~name], | ||||
|                         'comment': doc[~comments][i][~comments.content] | ||||
|                     }); | ||||
|                 } | ||||
|             }); | ||||
|             return comments; | ||||
|         } | ||||
|         """ | ||||
|  | ||||
|         sub_code = BlogPost.objects._sub_js_fields(code) | ||||
|         code_chunks = ['doc["cmnts"];', 'doc["doc-name"],', | ||||
|                        'doc["cmnts"][i]["body"]'] | ||||
|         for chunk in code_chunks: | ||||
|             self.assertTrue(chunk in sub_code) | ||||
|  | ||||
|         results = BlogPost.objects.exec_js(code) | ||||
|         expected_results = [ | ||||
|             {u'comment': u'cool', u'document': u'post1'}, | ||||
|             {u'comment': u'yay', u'document': u'post1'}, | ||||
|             {u'comment': u'nice stuff', u'document': u'post2'}, | ||||
|         ] | ||||
|         self.assertEqual(results, expected_results) | ||||
|  | ||||
|         BlogPost.drop_collection() | ||||
|  | ||||
|     def test_delete(self): | ||||
|         """Ensure that documents are properly deleted from the database. | ||||
|         """ | ||||
| @@ -297,12 +635,216 @@ class QuerySetTest(unittest.TestCase): | ||||
|         ages = [p.age for p in self.Person.objects.order_by('-name')] | ||||
|         self.assertEqual(ages, [30, 40, 20]) | ||||
|  | ||||
|     def test_map_reduce(self): | ||||
|         """Ensure map/reduce is both mapping and reducing. | ||||
|         """ | ||||
|         class BlogPost(Document): | ||||
|             title = StringField() | ||||
|             tags = ListField(StringField(), db_field='post-tag-list') | ||||
|  | ||||
|         BlogPost.drop_collection() | ||||
|  | ||||
|         BlogPost(title="Post #1", tags=['music', 'film', 'print']).save() | ||||
|         BlogPost(title="Post #2", tags=['music', 'film']).save() | ||||
|         BlogPost(title="Post #3", tags=['film', 'photography']).save() | ||||
|  | ||||
|         map_f = """ | ||||
|             function() { | ||||
|                 this[~tags].forEach(function(tag) { | ||||
|                     emit(tag, 1); | ||||
|                 }); | ||||
|             } | ||||
|         """ | ||||
|  | ||||
|         reduce_f = """ | ||||
|             function(key, values) { | ||||
|                 var total = 0; | ||||
|                 for(var i=0; i<values.length; i++) { | ||||
|                     total += values[i]; | ||||
|                 } | ||||
|                 return total; | ||||
|             } | ||||
|         """ | ||||
|  | ||||
|         # run a map/reduce operation spanning all posts | ||||
|         results = BlogPost.objects.map_reduce(map_f, reduce_f) | ||||
|         results = list(results) | ||||
|         self.assertEqual(len(results), 4) | ||||
|  | ||||
|         music = filter(lambda r: r.key == "music", results)[0] | ||||
|         self.assertEqual(music.value, 2) | ||||
|  | ||||
|         film = filter(lambda r: r.key == "film", results)[0] | ||||
|         self.assertEqual(film.value, 3) | ||||
|  | ||||
|         BlogPost.drop_collection() | ||||
|          | ||||
|     def test_map_reduce_with_custom_object_ids(self): | ||||
|         """Ensure that QuerySet.map_reduce works properly with custom | ||||
|         primary keys. | ||||
|         """ | ||||
|  | ||||
|         class BlogPost(Document): | ||||
|             title = StringField(primary_key=True) | ||||
|             tags = ListField(StringField()) | ||||
|          | ||||
|         post1 = BlogPost(title="Post #1", tags=["mongodb", "mongoengine"]) | ||||
|         post2 = BlogPost(title="Post #2", tags=["django", "mongodb"]) | ||||
|         post3 = BlogPost(title="Post #3", tags=["hitchcock films"]) | ||||
|          | ||||
|         post1.save() | ||||
|         post2.save() | ||||
|         post3.save() | ||||
|          | ||||
|         self.assertEqual(BlogPost._fields['title'].db_field, '_id') | ||||
|         self.assertEqual(BlogPost._meta['id_field'], 'title') | ||||
|          | ||||
|         map_f = """ | ||||
|             function() { | ||||
|                 emit(this._id, 1); | ||||
|             } | ||||
|         """ | ||||
|          | ||||
|         # reduce to a list of tag ids and counts | ||||
|         reduce_f = """ | ||||
|             function(key, values) { | ||||
|                 var total = 0; | ||||
|                 for(var i=0; i<values.length; i++) { | ||||
|                     total += values[i]; | ||||
|                 } | ||||
|                 return total; | ||||
|             } | ||||
|         """ | ||||
|          | ||||
|         results = BlogPost.objects.map_reduce(map_f, reduce_f) | ||||
|         results = list(results) | ||||
|          | ||||
|         self.assertEqual(results[0].object, post1) | ||||
|         self.assertEqual(results[1].object, post2) | ||||
|         self.assertEqual(results[2].object, post3) | ||||
|  | ||||
|         BlogPost.drop_collection() | ||||
|  | ||||
|     def test_map_reduce_finalize(self): | ||||
|         """Ensure that map, reduce, and finalize run and introduce "scope" | ||||
|         by simulating "hotness" ranking with Reddit algorithm. | ||||
|         """ | ||||
|         from time import mktime | ||||
|  | ||||
|         class Link(Document): | ||||
|             title = StringField(db_field='bpTitle') | ||||
|             up_votes = IntField() | ||||
|             down_votes = IntField() | ||||
|             submitted = DateTimeField(db_field='sTime') | ||||
|  | ||||
|         Link.drop_collection() | ||||
|  | ||||
|         now = datetime.utcnow() | ||||
|  | ||||
|         # Note: Test data taken from a custom Reddit homepage on | ||||
|         # Fri, 12 Feb 2010 14:36:00 -0600. Link ordering should | ||||
|         # reflect order of insertion below, but is not influenced | ||||
|         # by insertion order. | ||||
|         Link(title = "Google Buzz auto-followed a woman's abusive ex ...", | ||||
|              up_votes = 1079, | ||||
|              down_votes = 553, | ||||
|              submitted = now-timedelta(hours=4)).save() | ||||
|         Link(title = "We did it! Barbie is a computer engineer.", | ||||
|              up_votes = 481, | ||||
|              down_votes = 124, | ||||
|              submitted = now-timedelta(hours=2)).save() | ||||
|         Link(title = "This Is A Mosquito Getting Killed By A Laser", | ||||
|              up_votes = 1446, | ||||
|              down_votes = 530, | ||||
|              submitted=now-timedelta(hours=13)).save() | ||||
|         Link(title = "Arabic flashcards land physics student in jail.", | ||||
|              up_votes = 215, | ||||
|              down_votes = 105, | ||||
|              submitted = now-timedelta(hours=6)).save() | ||||
|         Link(title = "The Burger Lab: Presenting, the Flood Burger", | ||||
|              up_votes = 48, | ||||
|              down_votes = 17, | ||||
|              submitted = now-timedelta(hours=5)).save() | ||||
|         Link(title="How to see polarization with the naked eye", | ||||
|              up_votes = 74, | ||||
|              down_votes = 13, | ||||
|              submitted = now-timedelta(hours=10)).save() | ||||
|  | ||||
|         map_f = """ | ||||
|             function() { | ||||
|                 emit(this[~id], {up_delta: this[~up_votes] - this[~down_votes], | ||||
|                                 sub_date: this[~submitted].getTime() / 1000}) | ||||
|             } | ||||
|         """ | ||||
|  | ||||
|         reduce_f = """ | ||||
|             function(key, values) { | ||||
|                 data = values[0]; | ||||
|  | ||||
|                 x = data.up_delta; | ||||
|  | ||||
|                 // calculate time diff between reddit epoch and submission | ||||
|                 sec_since_epoch = data.sub_date - reddit_epoch; | ||||
|  | ||||
|                 // calculate 'Y' | ||||
|                 if(x > 0) { | ||||
|                     y = 1; | ||||
|                 } else if (x = 0) { | ||||
|                     y = 0; | ||||
|                 } else { | ||||
|                     y = -1; | ||||
|                 } | ||||
|  | ||||
|                 // calculate 'Z', the maximal value | ||||
|                 if(Math.abs(x) >= 1) { | ||||
|                     z = Math.abs(x); | ||||
|                 } else { | ||||
|                     z = 1; | ||||
|                 } | ||||
|  | ||||
|                 return {x: x, y: y, z: z, t_s: sec_since_epoch}; | ||||
|             } | ||||
|         """ | ||||
|  | ||||
|         finalize_f = """ | ||||
|             function(key, value) { | ||||
|                 // f(sec_since_epoch,y,z) =  | ||||
|                 //                    log10(z) + ((y*sec_since_epoch) / 45000) | ||||
|                 z_10 = Math.log(value.z) / Math.log(10); | ||||
|                 weight = z_10 + ((value.y * value.t_s) / 45000); | ||||
|                 return weight; | ||||
|             } | ||||
|         """ | ||||
|  | ||||
|         # provide the reddit epoch (used for ranking) as a variable available | ||||
|         # to all phases of the map/reduce operation: map, reduce, and finalize. | ||||
|         reddit_epoch = mktime(datetime(2005, 12, 8, 7, 46, 43).timetuple()) | ||||
|         scope = {'reddit_epoch': reddit_epoch} | ||||
|  | ||||
|         # run a map/reduce operation across all links. ordering is set | ||||
|         # to "-value", which orders the "weight" value returned from | ||||
|         # "finalize_f" in descending order. | ||||
|         results = Link.objects.order_by("-value") | ||||
|         results = results.map_reduce(map_f, | ||||
|                                      reduce_f, | ||||
|                                      finalize_f=finalize_f, | ||||
|                                      scope=scope) | ||||
|         results = list(results) | ||||
|  | ||||
|         # assert troublesome Buzz article is ranked 1st | ||||
|         self.assertTrue(results[0].object.title.startswith("Google Buzz")) | ||||
|  | ||||
|         # assert laser vision is ranked last | ||||
|         self.assertTrue(results[-1].object.title.startswith("How to see")) | ||||
|  | ||||
|         Link.drop_collection() | ||||
|  | ||||
|     def test_item_frequencies(self): | ||||
|         """Ensure that item frequencies are properly generated from lists. | ||||
|         """ | ||||
|         class BlogPost(Document): | ||||
|             hits = IntField() | ||||
|             tags = ListField(StringField(), name='blogTags') | ||||
|             tags = ListField(StringField(), db_field='blogTags') | ||||
|  | ||||
|         BlogPost.drop_collection() | ||||
|  | ||||
| @@ -364,7 +906,7 @@ class QuerySetTest(unittest.TestCase): | ||||
|             tags = ListField(StringField()) | ||||
|  | ||||
|             @queryset_manager | ||||
|             def music_posts(queryset): | ||||
|             def music_posts(doc_cls, queryset): | ||||
|                 return queryset(tags='music') | ||||
|  | ||||
|         BlogPost.drop_collection() | ||||
| @@ -387,12 +929,12 @@ class QuerySetTest(unittest.TestCase): | ||||
|         """Ensure that the correct field name is used when querying. | ||||
|         """ | ||||
|         class Comment(EmbeddedDocument): | ||||
|             content = StringField(name='commentContent') | ||||
|             content = StringField(db_field='commentContent') | ||||
|  | ||||
|         class BlogPost(Document): | ||||
|             title = StringField(name='postTitle') | ||||
|             title = StringField(db_field='postTitle') | ||||
|             comments = ListField(EmbeddedDocumentField(Comment), | ||||
|                                  name='postComments') | ||||
|                                  db_field='postComments') | ||||
|  | ||||
|  | ||||
|         BlogPost.drop_collection() | ||||
| @@ -438,6 +980,30 @@ class QuerySetTest(unittest.TestCase): | ||||
|  | ||||
|         BlogPost.drop_collection() | ||||
|  | ||||
|     def test_update_value_conversion(self): | ||||
|         """Ensure that values used in updates are converted before use. | ||||
|         """ | ||||
|         class Group(Document): | ||||
|             members = ListField(ReferenceField(self.Person)) | ||||
|  | ||||
|         Group.drop_collection() | ||||
|  | ||||
|         user1 = self.Person(name='user1') | ||||
|         user1.save() | ||||
|         user2 = self.Person(name='user2') | ||||
|         user2.save() | ||||
|  | ||||
|         group = Group() | ||||
|         group.save() | ||||
|  | ||||
|         Group.objects(id=group.id).update(set__members=[user1, user2]) | ||||
|         group.reload() | ||||
|  | ||||
|         self.assertTrue(len(group.members) == 2) | ||||
|         self.assertEqual(group.members[0].name, user1.name) | ||||
|         self.assertEqual(group.members[1].name, user2.name) | ||||
|  | ||||
|         Group.drop_collection() | ||||
|  | ||||
|     def test_types_index(self): | ||||
|         """Ensure that and index is used when '_types' is being used in a | ||||
| @@ -466,6 +1032,41 @@ class QuerySetTest(unittest.TestCase): | ||||
|  | ||||
|         BlogPost.drop_collection() | ||||
|  | ||||
|     def test_bulk(self): | ||||
|         """Ensure bulk querying by object id returns a proper dict. | ||||
|         """ | ||||
|         class BlogPost(Document): | ||||
|             title = StringField() | ||||
|  | ||||
|         BlogPost.drop_collection() | ||||
|  | ||||
|         post_1 = BlogPost(title="Post #1") | ||||
|         post_2 = BlogPost(title="Post #2") | ||||
|         post_3 = BlogPost(title="Post #3") | ||||
|         post_4 = BlogPost(title="Post #4") | ||||
|         post_5 = BlogPost(title="Post #5") | ||||
|  | ||||
|         post_1.save() | ||||
|         post_2.save() | ||||
|         post_3.save() | ||||
|         post_4.save() | ||||
|         post_5.save() | ||||
|  | ||||
|         ids = [post_1.id, post_2.id, post_5.id] | ||||
|         objects = BlogPost.objects.in_bulk(ids) | ||||
|  | ||||
|         self.assertEqual(len(objects), 3) | ||||
|  | ||||
|         self.assertTrue(post_1.id in objects) | ||||
|         self.assertTrue(post_2.id in objects) | ||||
|         self.assertTrue(post_5.id in objects) | ||||
|  | ||||
|         self.assertTrue(objects[post_1.id].title == post_1.title) | ||||
|         self.assertTrue(objects[post_2.id].title == post_2.title) | ||||
|         self.assertTrue(objects[post_5.id].title == post_5.title) | ||||
|  | ||||
|         BlogPost.drop_collection() | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.Person.drop_collection() | ||||
|  | ||||
| @@ -473,6 +1074,8 @@ class QuerySetTest(unittest.TestCase): | ||||
| class QTest(unittest.TestCase): | ||||
|  | ||||
|     def test_or_and(self): | ||||
|         """Ensure that Q objects may be combined correctly. | ||||
|         """ | ||||
|         q1 = Q(name='test') | ||||
|         q2 = Q(age__gte=18) | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user