├── tests
├── dbindexes.py
├── contrib
│ ├── views.py
│ ├── __init__.py
│ ├── utils.py
│ ├── models.py
│ └── tests.py
├── embedded
│ ├── views.py
│ ├── __init__.py
│ ├── utils.py
│ ├── tests.py
│ └── models.py
├── query
│ ├── __init__.py
│ ├── utils.py
│ └── models.py
├── storage
│ ├── models.py
│ ├── __init__.py
│ ├── utils.py
│ └── tests.py
├── aggregations
│ ├── views.py
│ ├── __init__.py
│ ├── utils.py
│ ├── models.py
│ └── tests.py
├── lookup
│ ├── __init__.py
│ └── models.py
├── mongodb
│ ├── __init__.py
│ ├── utils.py
│ └── models.py
├── router
│ ├── __init__.py
│ ├── models.py
│ └── tests.py
├── multiple_database
│ ├── utils.py
│ ├── __init__.py
│ ├── fixtures
│ │ ├── multidb-common.json
│ │ ├── pets.json
│ │ ├── multidb.other.json
│ │ └── multidb.default.json
│ └── models.py
├── settings
│ ├── slow_tests.py
│ ├── dbindexer.py
│ ├── sqlite.py
│ ├── router.py
│ ├── __init__.py
│ ├── settings_base.py
│ ├── debug.py
│ └── serializer.py
├── manage.py
├── utils.py
└── runtests.py
├── docs
├── source
│ ├── code
│ │ ├── mapreduce
│ │ │ ├── __init__.py
│ │ │ ├── mr
│ │ │ │ ├── __init__.py
│ │ │ │ ├── views.py
│ │ │ │ ├── models.py
│ │ │ │ └── tests.py
│ │ │ ├── settings.py
│ │ │ ├── manage.py
│ │ │ └── urls.py
│ │ └── tutorial
│ │ │ ├── v1
│ │ │ ├── __init__.py
│ │ │ ├── nonrelblog
│ │ │ │ ├── __init__.py
│ │ │ │ ├── views.py
│ │ │ │ ├── models.py
│ │ │ │ └── tests.py
│ │ │ ├── settings.py
│ │ │ ├── manage.py
│ │ │ └── urls.py
│ │ │ ├── v2
│ │ │ ├── __init__.py
│ │ │ ├── nonrelblog
│ │ │ │ ├── __init__.py
│ │ │ │ ├── views.py
│ │ │ │ ├── tests.py
│ │ │ │ └── models.py
│ │ │ ├── settings.py
│ │ │ ├── manage.py
│ │ │ └── urls.py
│ │ │ ├── v3
│ │ │ ├── __init__.py
│ │ │ ├── nonrelblog
│ │ │ │ ├── __init__.py
│ │ │ │ ├── templates
│ │ │ │ │ └── nonrelblog
│ │ │ │ │ │ ├── post_detail.html
│ │ │ │ │ │ └── post_list.html
│ │ │ │ ├── urls.py
│ │ │ │ └── models.py
│ │ │ ├── settings.py
│ │ │ ├── urls.py
│ │ │ └── manage.py
│ │ │ ├── v4
│ │ │ ├── __init__.py
│ │ │ ├── nonrelblog
│ │ │ │ ├── __init__.py
│ │ │ │ ├── templates
│ │ │ │ │ └── nonrelblog
│ │ │ │ │ │ ├── post_detail.html
│ │ │ │ │ │ └── post_list.html
│ │ │ │ ├── urls.py
│ │ │ │ └── models.py
│ │ │ ├── upload.png
│ │ │ ├── itworks.png
│ │ │ ├── gridfsuploads
│ │ │ │ ├── __init__.py
│ │ │ │ ├── admin.py
│ │ │ │ ├── urls.py
│ │ │ │ ├── models.py
│ │ │ │ └── views.py
│ │ │ ├── settings.py
│ │ │ ├── manage.py
│ │ │ └── urls.py
│ │ │ └── v7
│ │ │ ├── __init__.py
│ │ │ ├── nonrelblog
│ │ │ ├── __init__.py
│ │ │ ├── templates
│ │ │ │ └── nonrelblog
│ │ │ │ │ ├── post_detail.html
│ │ │ │ │ └── post_list.html
│ │ │ ├── urls.py
│ │ │ ├── models.py
│ │ │ └── tests.py
│ │ │ ├── settings.py
│ │ │ ├── urls.py
│ │ │ └── manage.py
│ ├── mongodbtheme
│ │ ├── relations.html
│ │ ├── sourcelink.html
│ │ ├── theme.conf
│ │ ├── localtoc.html
│ │ ├── static
│ │ │ ├── Qlassik_TB.otf
│ │ │ ├── QlassikBold_TB.otf
│ │ │ └── mongodb.css
│ │ ├── sidebar.html
│ │ └── layout.html
│ ├── meta
│ │ ├── authors.rst
│ │ ├── changelog.rst
│ │ ├── index.rst
│ │ └── contributing.rst
│ ├── reference
│ │ ├── storage.rst
│ │ ├── index.rst
│ │ ├── mapreduce.rst
│ │ ├── lowerlevel.rst
│ │ ├── fields.rst
│ │ ├── settings.rst
│ │ └── model-options.rst
│ ├── topics
│ │ ├── index.rst
│ │ ├── atomic-updates.rst
│ │ ├── aggregations.rst
│ │ ├── cache.rst
│ │ ├── setup.rst
│ │ ├── gridfs.rst
│ │ ├── lists-and-dicts.rst
│ │ ├── embedded-models.rst
│ │ ├── mapreduce.rst
│ │ └── lowerlevel.rst
│ ├── utils.py
│ ├── index.rst
│ ├── doc-organization.rst
│ ├── conf.py
│ └── troubleshooting.rst
├── make.bat
└── Makefile
├── django_mongodb_engine
├── contrib
│ ├── search
│ │ ├── __init__.py
│ │ ├── tokenizer.py
│ │ └── fields.py
│ └── __init__.py
├── management
│ ├── __init__.py
│ └── commands
│ │ ├── __init__.py
│ │ └── tellsiteid.py
├── models.py
├── query.py
├── aggregations.py
├── south.py
├── router.py
├── __init__.py
├── utils.py
├── south_adapter.py
├── storage.py
├── fields.py
├── creation.py
└── base.py
├── MANIFEST.in
├── requirements.txt
├── .gitignore
├── AUTHORS.rst
├── README.rst
├── .travis.yml
├── setup.py
└── CHANGELOG.rst
/tests/dbindexes.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/contrib/views.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/embedded/views.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/query/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/storage/models.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/aggregations/views.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/contrib/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/embedded/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/lookup/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/mongodb/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/router/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/storage/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/aggregations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/contrib/utils.py:
--------------------------------------------------------------------------------
1 | ../utils.py
--------------------------------------------------------------------------------
/tests/mongodb/utils.py:
--------------------------------------------------------------------------------
1 | ../utils.py
--------------------------------------------------------------------------------
/tests/query/utils.py:
--------------------------------------------------------------------------------
1 | ../utils.py
--------------------------------------------------------------------------------
/tests/storage/utils.py:
--------------------------------------------------------------------------------
1 | ../utils.py
--------------------------------------------------------------------------------
/docs/source/code/mapreduce/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/aggregations/utils.py:
--------------------------------------------------------------------------------
1 | ../utils.py
--------------------------------------------------------------------------------
/tests/embedded/utils.py:
--------------------------------------------------------------------------------
1 | ../utils.py
--------------------------------------------------------------------------------
/docs/source/code/mapreduce/mr/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v1/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v2/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v3/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v4/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v7/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/source/mongodbtheme/relations.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/source/mongodbtheme/sourcelink.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/multiple_database/utils.py:
--------------------------------------------------------------------------------
1 | ../utils.py
--------------------------------------------------------------------------------
/django_mongodb_engine/contrib/search/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/django_mongodb_engine/management/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/source/meta/authors.rst:
--------------------------------------------------------------------------------
1 | ../../../AUTHORS.rst
--------------------------------------------------------------------------------
/django_mongodb_engine/management/commands/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v1/nonrelblog/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v2/nonrelblog/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v3/nonrelblog/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v4/nonrelblog/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v7/nonrelblog/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/source/meta/changelog.rst:
--------------------------------------------------------------------------------
1 | ../../../CHANGELOG.rst
--------------------------------------------------------------------------------
/docs/source/code/mapreduce/mr/views.py:
--------------------------------------------------------------------------------
1 | # Create your views here.
2 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v1/nonrelblog/views.py:
--------------------------------------------------------------------------------
1 | # Create your views here.
2 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v2/nonrelblog/views.py:
--------------------------------------------------------------------------------
1 | # Create your views here.
2 |
--------------------------------------------------------------------------------
/docs/source/mongodbtheme/theme.conf:
--------------------------------------------------------------------------------
1 | [theme]
2 | inherit = basic
3 | stylesheet = mongodb.css
4 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include MANIFEST.in
2 | include README.rst
3 | include AUTHORS.rst
4 | include CHANGELOG.rst
5 |
--------------------------------------------------------------------------------
/tests/multiple_database/__init__.py:
--------------------------------------------------------------------------------
1 | """Stolen from the Django testsuite, shaked down for m2m tests."""
2 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v4/upload.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-nonrel/mongodb-engine/HEAD/docs/source/code/tutorial/v4/upload.png
--------------------------------------------------------------------------------
/docs/source/mongodbtheme/localtoc.html:
--------------------------------------------------------------------------------
1 | {%- if display_toc %}
2 |
{{ _('Table Of Contents') }}
3 | {{ toc }}
4 | {%- endif %}
5 |
--------------------------------------------------------------------------------
/docs/source/reference/storage.rst:
--------------------------------------------------------------------------------
1 | GridFS Storage
2 | ==============
3 |
4 | .. autoclass:: django_mongodb_engine.storage.GridFSStorage
5 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v4/itworks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-nonrel/mongodb-engine/HEAD/docs/source/code/tutorial/v4/itworks.png
--------------------------------------------------------------------------------
/docs/source/meta/index.rst:
--------------------------------------------------------------------------------
1 | Meta
2 | ====
3 |
4 | .. toctree::
5 | :maxdepth: 1
6 |
7 | changelog
8 | contributing
9 | authors
10 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | pymongo>=2.8
2 | djangotoolbox>=1.6.0
3 | django-dbindexer>=1.6.0
4 | https://github.com/django-nonrel/django/tarball/nonrel-1.6
5 |
--------------------------------------------------------------------------------
/docs/source/mongodbtheme/static/Qlassik_TB.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-nonrel/mongodb-engine/HEAD/docs/source/mongodbtheme/static/Qlassik_TB.otf
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v4/gridfsuploads/__init__.py:
--------------------------------------------------------------------------------
1 | from django_mongodb_engine.storage import GridFSStorage
2 |
3 |
4 | gridfs_storage = GridFSStorage()
5 |
--------------------------------------------------------------------------------
/docs/source/mongodbtheme/static/QlassikBold_TB.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-nonrel/mongodb-engine/HEAD/docs/source/mongodbtheme/static/QlassikBold_TB.otf
--------------------------------------------------------------------------------
/tests/aggregations/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 |
4 | class Person(models.Model):
5 | age = models.IntegerField()
6 | birthday = models.DateTimeField()
7 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v4/gridfsuploads/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib.admin import site
2 |
3 | from gridfsuploads.models import FileUpload
4 |
5 |
6 | site.register(FileUpload)
7 |
--------------------------------------------------------------------------------
/docs/source/code/mapreduce/settings.py:
--------------------------------------------------------------------------------
1 | DATABASES = {
2 | 'default': {
3 | 'ENGINE': 'django_mongodb_engine',
4 | 'NAME': 'mapreduce',
5 | },
6 | }
7 |
8 | INSTALLED_APPS = ['mr']
9 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v1/settings.py:
--------------------------------------------------------------------------------
1 | DATABASES = {
2 | 'default': {
3 | 'ENGINE': 'django_mongodb_engine',
4 | 'NAME': 'tutorial',
5 | },
6 | }
7 |
8 | INSTALLED_APPS = ['nonrelblog']
9 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v2/settings.py:
--------------------------------------------------------------------------------
1 | DATABASES = {
2 | 'default': {
3 | 'ENGINE': 'django_mongodb_engine',
4 | 'NAME': 'tutorial',
5 | },
6 | }
7 |
8 | INSTALLED_APPS = ['nonrelblog']
9 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v4/gridfsuploads/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import patterns, url
2 |
3 |
4 | urlpatterns = patterns('gridfsuploads.views',
5 | ('^(?P.+)', 'serve_from_gridfs'),
6 | )
7 |
--------------------------------------------------------------------------------
/docs/source/reference/index.rst:
--------------------------------------------------------------------------------
1 | Reference
2 | =========
3 |
4 | .. toctree::
5 | :maxdepth: 2
6 |
7 | fields
8 | storage
9 | mapreduce
10 | settings
11 | model-options
12 | lowerlevel
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *~
3 | .db*
4 | .pydevproject
5 | .settings
6 | .project
7 | django_mongodb_engine.egg-info
8 | build
9 | .build
10 | docs/.*
11 | dist/
12 | .tox
13 |
14 | MANIFEST
15 |
16 |
17 | .env
18 | .idea
--------------------------------------------------------------------------------
/tests/settings/slow_tests.py:
--------------------------------------------------------------------------------
1 | from settings import *
2 |
3 |
4 | INSTALLED_APPS = DEFAULT_APPS + ['django.contrib.contenttypes', 'multiple_database']
5 | ROOT_URLCONF = ''
6 | TEST_RUNNER = 'djangotoolbox.test.NonrelTestSuiteRunner'
7 |
--------------------------------------------------------------------------------
/docs/source/topics/index.rst:
--------------------------------------------------------------------------------
1 | Topics
2 | ======
3 |
4 | .. toctree::
5 | :maxdepth: 2
6 |
7 | lists-and-dicts
8 | embedded-models
9 | atomic-updates
10 | gridfs
11 | mapreduce
12 | cache
13 | aggregations
14 | lowerlevel
15 |
--------------------------------------------------------------------------------
/tests/multiple_database/fixtures/multidb-common.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "pk": 1,
4 | "model": "multiple_database.book",
5 | "fields": {
6 | "title": "The Definitive Guide to Django",
7 | "published": "2009-7-8"
8 | }
9 | }
10 | ]
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v3/settings.py:
--------------------------------------------------------------------------------
1 | DATABASES = {
2 | 'default': {
3 | 'ENGINE': 'django_mongodb_engine',
4 | 'NAME': 'tutorial',
5 | }
6 | },
7 |
8 | INSTALLED_APPS = ['nonrelblog']
9 |
10 | ROOT_URLCONF = 'urls'
11 |
12 | DEBUG = TEMPLATE_DEBUG = True
13 |
--------------------------------------------------------------------------------
/tests/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | if __name__ == "__main__":
6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
7 |
8 | from django.core.management import execute_from_command_line
9 |
10 | execute_from_command_line(sys.argv)
11 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v1/nonrelblog/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | from djangotoolbox.fields import ListField
4 |
5 |
6 | class Post(models.Model):
7 | title = models.CharField()
8 | text = models.TextField()
9 | tags = ListField()
10 | comments = ListField()
11 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v4/gridfsuploads/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | from gridfsuploads import gridfs_storage
4 |
5 |
6 | class FileUpload(models.Model):
7 | created_on = models.DateTimeField(auto_now_add=True)
8 | file = models.FileField(storage=gridfs_storage, upload_to='/')
9 |
--------------------------------------------------------------------------------
/tests/settings/dbindexer.py:
--------------------------------------------------------------------------------
1 | from settings import *
2 |
3 |
4 | DATABASES['mongodb'] = DATABASES['default']
5 | DATABASES['default'] = {'ENGINE': 'dbindexer', 'TARGET': 'mongodb'}
6 |
7 | ROOT_URLCONF = ''
8 | DBINDEXER_SITECONF = 'dbindexes'
9 |
10 | INSTALLED_APPS = INSTALLED_APPS + ['dbindexer']
11 |
--------------------------------------------------------------------------------
/tests/router/models.py:
--------------------------------------------------------------------------------
1 | from django.db.models import Model
2 |
3 | from djangotoolbox.fields import RawField
4 |
5 |
6 | class SQLiteModel(Model):
7 | pass
8 |
9 |
10 | class MongoDBModel(Model):
11 | # Wnsure this goes to MongoDB on syncdb: SQLite can't
12 | # handle RawFields.
13 | o = RawField()
14 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v3/nonrelblog/templates/nonrelblog/post_detail.html:
--------------------------------------------------------------------------------
1 | {{ post.title }}
2 | {{ post.created_on }}
3 | {{ post.text }}
4 |
Comments
5 | {% for comment in post.comments %}
6 | {{ comment.author.name }} on {{ comment.created_on }}
7 | {{ comment.text }}
8 | {% endfor %}
9 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v4/nonrelblog/templates/nonrelblog/post_detail.html:
--------------------------------------------------------------------------------
1 | {{ post.title }}
2 | {{ post.created_on }}
3 | {{ post.text }}
4 |
Comments
5 | {% for comment in post.comments %}
6 | {{ comment.author.name }} on {{ comment.created_on }}
7 | {{ comment.text }}
8 | {% endfor %}
9 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v7/nonrelblog/templates/nonrelblog/post_detail.html:
--------------------------------------------------------------------------------
1 | {{ post.title }}
2 | {{ post.created_on }}
3 | {{ post.text }}
4 |
Comments
5 | {% for comment in post.comments %}
6 | {{ comment.author.name }} on {{ comment.created_on }}
7 | {{ comment.text }}
8 | {% endfor %}
9 |
--------------------------------------------------------------------------------
/tests/settings/sqlite.py:
--------------------------------------------------------------------------------
1 | from settings import *
2 |
3 |
4 | DATABASES = {
5 | 'default': {
6 | 'NAME': 'test',
7 | 'ENGINE': 'sqlite3',
8 | },
9 | }
10 | for app in ['embedded', 'storage']:
11 | INSTALLED_APPS.remove(app)
12 |
13 | DATABASES['mongodb'] = {'NAME': 'mongodb', 'ENGINE': 'django_mongodb_engine'}
14 |
--------------------------------------------------------------------------------
/docs/source/code/mapreduce/mr/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | from django_mongodb_engine.contrib import MongoDBManager
4 |
5 |
6 | class Article(models.Model):
7 | author = models.ForeignKey('Author')
8 | text = models.TextField()
9 |
10 | objects = MongoDBManager()
11 |
12 |
13 | class Author(models.Model):
14 | pass
15 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v3/nonrelblog/templates/nonrelblog/post_list.html:
--------------------------------------------------------------------------------
1 | Post Overview
2 | {% for post in post_list %}
3 |
4 |
5 | {{ post.created_on }} |
6 | {{ post.comments|length }} comments |
7 | tagged {{ post.tags|join:', ' }}
8 |
9 | {% endfor %}
10 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v4/nonrelblog/templates/nonrelblog/post_list.html:
--------------------------------------------------------------------------------
1 | Post Overview
2 | {% for post in post_list %}
3 |
4 |
5 | {{ post.created_on }} |
6 | {{ post.comments|length }} comments |
7 | tagged {{ post.tags|join:', ' }}
8 |
9 | {% endfor %}
10 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v7/nonrelblog/templates/nonrelblog/post_list.html:
--------------------------------------------------------------------------------
1 | Post Overview
2 | {% for post in post_list %}
3 |
4 |
5 | {{ post.created_on }} |
6 | {{ post.comments|length }} comments |
7 | tagged {{ post.tags|join:', ' }}
8 |
9 | {% endfor %}
10 |
--------------------------------------------------------------------------------
/tests/settings/router.py:
--------------------------------------------------------------------------------
1 | from settings import *
2 |
3 |
4 | INSTALLED_APPS = ['query', 'router']
5 |
6 | DATABASES['default'] = {'ENGINE': 'django.db.backends.sqlite3', 'NAME': '/tmp/db.sql'}
7 |
8 | DATABASE_ROUTERS = ['django_mongodb_engine.router.MongoDBRouter']
9 | MONGODB_MANAGED_APPS = ['query']
10 | MONGODB_MANAGED_MODELS = ['router.MongoDBModel']
11 |
--------------------------------------------------------------------------------
/docs/source/mongodbtheme/sidebar.html:
--------------------------------------------------------------------------------
1 | ⚠ Documentation version
2 |
3 | These are the Git version docs.
4 | Docs for 0.4 (PyPI) are here.
5 |
6 |
7 | Help out!
8 |
9 | To make this documentation even better, we'd love to receive your
10 | feedback and suggestions for improvement!
11 |
12 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v7/settings.py:
--------------------------------------------------------------------------------
1 | DATABASES = {
2 | 'default': {
3 | 'ENGINE': 'django_mongodb_engine',
4 | 'NAME': 'tutorial',
5 | },
6 | }
7 |
8 | INSTALLED_APPS = [
9 | 'django.contrib.contenttypes',
10 | 'django.contrib.auth',
11 | 'django.contrib.admin',
12 |
13 | 'nonrelblog',
14 | ]
15 |
16 | ROOT_URLCONF = 'urls'
17 |
18 | DEBUG = TEMPLATE_DEBUG = True
19 |
--------------------------------------------------------------------------------
/tests/multiple_database/fixtures/pets.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "pk": 1,
4 | "model": "multiple_database.pet",
5 | "fields": {
6 | "name": "Mr Bigglesworth",
7 | "owner": 1
8 | }
9 | },
10 | {
11 | "pk": 2,
12 | "model": "multiple_database.pet",
13 | "fields": {
14 | "name": "Spot",
15 | "owner": 2
16 | }
17 | }
18 | ]
--------------------------------------------------------------------------------
/tests/settings/__init__.py:
--------------------------------------------------------------------------------
1 | from settings_base import *
2 |
3 |
4 | DEFAULT_APPS = [
5 | 'django.contrib.auth',
6 | 'django.contrib.sessions',
7 | 'django.contrib.messages',
8 | ]
9 |
10 | INSTALLED_APPS = [
11 | 'django_mongodb_engine',
12 | 'djangotoolbox',
13 | 'query',
14 | 'lookup',
15 | 'embedded',
16 | 'mongodb',
17 | 'aggregations',
18 | 'contrib',
19 | 'storage',
20 | ]
21 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v4/settings.py:
--------------------------------------------------------------------------------
1 | DATABASES = {
2 | 'default': {
3 | 'ENGINE': 'django_mongodb_engine',
4 | 'NAME': 'tutorial',
5 | },
6 | }
7 |
8 | INSTALLED_APPS = [
9 | 'django.contrib.contenttypes',
10 | 'django.contrib.auth',
11 | 'django.contrib.admin',
12 |
13 | 'nonrelblog',
14 | 'gridfsuploads',
15 | ]
16 |
17 | ROOT_URLCONF = 'urls'
18 |
19 | DEBUG = TEMPLATE_DEBUG = True
20 |
--------------------------------------------------------------------------------
/docs/source/utils.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from subprocess import CalledProcessError, check_output
3 |
4 |
5 | def get_current_year():
6 | return datetime.utcnow().year
7 |
8 |
9 | def get_git_head():
10 | try:
11 | return check_output(['git', 'rev-parse', 'HEAD'])
12 | except CalledProcessError:
13 | pass
14 | except OSError, exc:
15 | if exc.errno != 2:
16 | raise
17 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v7/nonrelblog/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import patterns, url
2 | from django.views.generic import ListView, DetailView
3 | from models import Post
4 |
5 | post_detail = DetailView.as_view(model=Post)
6 | post_list = ListView.as_view(model=Post)
7 |
8 | urlpatterns = patterns('',
9 | url(r'^post/(?P[a-z\d]+)/$', post_detail, name='post_detail'),
10 | url(r'^$', post_list, name='post_list'),
11 | )
12 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v3/nonrelblog/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import patterns, url
2 | from django.views.generic import ListView, DetailView
3 |
4 | from models import Post
5 |
6 |
7 | post_detail = DetailView.as_view(model=Post)
8 | post_list = ListView.as_view(model=Post)
9 |
10 | urlpatterns = patterns('',
11 | url(r'^post/(?P[a-z\d]+)/$', post_detail, name='post_detail'),
12 | url(r'^$', post_list, name='post_list'),
13 | )
14 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v4/nonrelblog/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import patterns, url
2 | from django.views.generic import ListView, DetailView
3 |
4 | from models import Post
5 |
6 |
7 | post_detail = DetailView.as_view(model=Post)
8 | post_list = ListView.as_view(model=Post)
9 |
10 | urlpatterns = patterns('',
11 | url(r'^post/(?P[a-z\d]+)/$', post_detail, name='post_detail'),
12 | url(r'^$', post_list, name='post_list'),
13 | )
14 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v7/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import patterns, include, url
2 | from django.contrib import admin
3 |
4 |
5 | admin.autodiscover()
6 |
7 | urlpatterns = patterns('',
8 | # Uncomment the admin/doc line below to enable admin documentation:
9 | # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
10 |
11 | url(r'^admin/', include(admin.site.urls)),
12 |
13 | url('', include('nonrelblog.urls')),
14 | )
15 |
--------------------------------------------------------------------------------
/tests/settings/settings_base.py:
--------------------------------------------------------------------------------
1 | DATABASES = {
2 | 'default': {
3 | 'ENGINE': 'django_mongodb_engine',
4 | 'NAME': 'test',
5 | 'OPTIONS': {'OPERATIONS': {}}
6 | },
7 | 'other': {
8 | 'ENGINE': 'django_mongodb_engine',
9 | 'NAME': 'test2'
10 | },
11 | }
12 |
13 | SERIALIZATION_MODULES = {'json': 'settings.serializer'}
14 |
15 | SECRET_KEY = 'super secret'
16 |
17 | try:
18 | from local_settings import *
19 | except ImportError:
20 | pass
21 |
--------------------------------------------------------------------------------
/tests/settings/debug.py:
--------------------------------------------------------------------------------
1 | from . import *
2 |
3 |
4 | TEST_DEBUG = True
5 |
6 | LOGGING = {
7 | 'version': 1,
8 | 'formatters': {'simple': {'format': '%(levelname)s %(message)s'}},
9 | 'handlers': {
10 | 'console': {
11 | 'level': 'DEBUG',
12 | 'class': 'logging.StreamHandler',
13 | 'formatter': 'simple',
14 | }
15 | },
16 | 'loggers': {
17 | 'django.db.backends': {
18 | 'level': 'DEBUG',
19 | 'handlers': ['console'],
20 | },
21 | },
22 | }
23 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v3/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import patterns, include, url
2 |
3 | # Uncomment the next two lines to enable the admin:
4 | # from django.contrib import admin
5 | # admin.autodiscover()
6 |
7 | urlpatterns = patterns('',
8 | # Uncomment the admin/doc line below to enable admin documentation:
9 | # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
10 |
11 | # Uncomment the next line to enable the admin:
12 | # url(r'^admin/', include(admin.site.urls)),
13 |
14 | url('', include('nonrelblog.urls')),
15 | )
16 |
--------------------------------------------------------------------------------
/tests/embedded/tests.py:
--------------------------------------------------------------------------------
1 | from django_mongodb_engine.query import A
2 |
3 | from models import *
4 | from utils import TestCase, get_collection
5 |
6 |
7 | class EmbeddedModelFieldTestCase(TestCase):
8 |
9 | def test_query_embedded(self):
10 | Model(x=3, em=EmbeddedModel(charfield='foo')).save()
11 | obj = Model(x=3, em=EmbeddedModel(charfield='blurg'))
12 | obj.save()
13 | Model(x=3, em=EmbeddedModel(charfield='bar')).save()
14 | obj_from_db = Model.objects.get(em=A('charfield', 'blurg'))
15 | self.assertEqual(obj, obj_from_db)
16 |
--------------------------------------------------------------------------------
/docs/source/code/mapreduce/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from django.core.management import execute_manager
3 | import imp
4 | try:
5 | imp.find_module('settings') # Assumed to be in the same directory.
6 | except ImportError:
7 | import sys
8 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__)
9 | sys.exit(1)
10 |
11 | import settings
12 |
13 | if __name__ == "__main__":
14 | execute_manager(settings)
15 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v1/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from django.core.management import execute_manager
3 | import imp
4 | try:
5 | imp.find_module('settings') # Assumed to be in the same directory.
6 | except ImportError:
7 | import sys
8 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__)
9 | sys.exit(1)
10 |
11 | import settings
12 |
13 | if __name__ == "__main__":
14 | execute_manager(settings)
15 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v2/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from django.core.management import execute_manager
3 | import imp
4 | try:
5 | imp.find_module('settings') # Assumed to be in the same directory.
6 | except ImportError:
7 | import sys
8 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__)
9 | sys.exit(1)
10 |
11 | import settings
12 |
13 | if __name__ == "__main__":
14 | execute_manager(settings)
15 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v3/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from django.core.management import execute_manager
3 | import imp
4 | try:
5 | imp.find_module('settings') # Assumed to be in the same directory.
6 | except ImportError:
7 | import sys
8 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__)
9 | sys.exit(1)
10 |
11 | import settings
12 |
13 | if __name__ == "__main__":
14 | execute_manager(settings)
15 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v4/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from django.core.management import execute_manager
3 | import imp
4 | try:
5 | imp.find_module('settings') # Assumed to be in the same directory.
6 | except ImportError:
7 | import sys
8 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__)
9 | sys.exit(1)
10 |
11 | import settings
12 |
13 | if __name__ == "__main__":
14 | execute_manager(settings)
15 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v7/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from django.core.management import execute_manager
3 | import imp
4 | try:
5 | imp.find_module('settings') # Assumed to be in the same directory.
6 | except ImportError:
7 | import sys
8 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__)
9 | sys.exit(1)
10 |
11 | import settings
12 |
13 | if __name__ == "__main__":
14 | execute_manager(settings)
15 |
--------------------------------------------------------------------------------
/docs/source/index.rst:
--------------------------------------------------------------------------------
1 | Welcome
2 | =======
3 |
4 | Django MongoDB Engine is a MongoDB_ backend for Django_,
5 | the Python Web framework for perfectionists with deadlines.
6 |
7 | This documentation is split into several sections:
8 |
9 | .. include:: doc-organization.rst
10 |
11 | Contents
12 | --------
13 | .. toctree::
14 | :maxdepth: 2
15 | :titlesonly:
16 |
17 | Intro
18 | tutorial
19 | topics/setup
20 | topics/index
21 | reference/index
22 | troubleshooting
23 | meta/index
24 |
25 | .. _MongoDB: http://mongodb.org
26 | .. _Django: http://djangoproject.com
27 |
--------------------------------------------------------------------------------
/docs/source/reference/mapreduce.rst:
--------------------------------------------------------------------------------
1 | Map/Reduce
2 | ==========
3 | ::
4 |
5 | from django_mongodb_engine.contrib import MongoDBManager
6 |
7 | class MapReduceableModel(models.Model):
8 | ...
9 | objects = MongoDBManager()
10 |
11 | ::
12 |
13 | >>> MapReduceableModel.objects.filter(...).map_reduce(...)
14 |
15 | .. currentmodule:: django_mongodb_engine.contrib
16 |
17 | .. TODO Should be documented as MongoDBManager.map_reduce
18 | .. automethod:: MongoDBQuerySet.map_reduce(...)
19 |
20 | .. automethod:: MongoDBQuerySet.inline_map_reduce(...)
21 |
22 | .. autoclass:: MapReduceResult
23 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v1/nonrelblog/tests.py:
--------------------------------------------------------------------------------
1 | __test__ = {'v1': """
2 | >>> from nonrelblog.models import Post
3 | >>> post = Post.objects.create(
4 | ... title='Hello MongoDB!',
5 | ... text='Just wanted to drop a note from Django. Cya!',
6 | ... tags=['mongodb', 'django']
7 | ... )
8 |
9 | Surely we want to add some comments.
10 |
11 | >>> post.comments
12 | []
13 | >>> post.comments.extend(['Great post!', 'Please, do more of these!'])
14 | >>> post.save()
15 |
16 | Look and see, it has actually been saved!
17 |
18 | >>> Post.objects.get().comments
19 | [u'Great post!', u'Please, do more of these!']
20 | """}
21 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v4/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import patterns, include, url
2 |
3 | # Uncomment the next two lines to enable the admin:
4 | from django.contrib import admin
5 | admin.autodiscover()
6 |
7 | urlpatterns = patterns('',
8 | # Uncomment the admin/doc line below to enable admin documentation:
9 | # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
10 |
11 | # Uncomment the next line to enable the admin:
12 | url(r'^admin/', include(admin.site.urls)),
13 |
14 | url('^uploads/', include('gridfsuploads.urls')),
15 | url('', include('nonrelblog.urls')),
16 | )
17 |
--------------------------------------------------------------------------------
/tests/multiple_database/fixtures/multidb.other.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "pk": 1,
4 | "model": "multiple_database.person",
5 | "fields": {
6 | "name": "Mark Pilgrim"
7 | }
8 | },
9 | {
10 | "pk": 2,
11 | "model": "multiple_database.person",
12 | "fields": {
13 | "name": "Chris Mills"
14 | }
15 | },
16 | {
17 | "pk": 2,
18 | "model": "multiple_database.book",
19 | "fields": {
20 | "title": "Dive into Python",
21 | "published": "2009-5-4",
22 | "editor": ["Chris Mills"]
23 | }
24 | }
25 | ]
26 |
--------------------------------------------------------------------------------
/docs/source/code/mapreduce/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import patterns, include, url
2 |
3 |
4 | # Uncomment the next two lines to enable the admin:
5 | # from django.contrib import admin
6 | # admin.autodiscover()
7 |
8 | urlpatterns = patterns('',
9 | # Examples:
10 | # url(r'^$', 'v1.views.home', name='home'),
11 | # url(r'^v1/', include('v1.foo.urls')),
12 |
13 | # Uncomment the admin/doc line below to enable admin documentation:
14 | # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
15 |
16 | # Uncomment the next line to enable the admin:
17 | # url(r'^admin/', include(admin.site.urls)),
18 | )
19 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v1/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import patterns, include, url
2 |
3 |
4 | # Uncomment the next two lines to enable the admin:
5 | # from django.contrib import admin
6 | # admin.autodiscover()
7 |
8 | urlpatterns = patterns('',
9 | # Examples:
10 | # url(r'^$', 'v1.views.home', name='home'),
11 | # url(r'^v1/', include('v1.foo.urls')),
12 |
13 | # Uncomment the admin/doc line below to enable admin documentation:
14 | # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
15 |
16 | # Uncomment the next line to enable the admin:
17 | # url(r'^admin/', include(admin.site.urls)),
18 | )
19 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v2/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import patterns, include, url
2 |
3 |
4 | # Uncomment the next two lines to enable the admin:
5 | # from django.contrib import admin
6 | # admin.autodiscover()
7 |
8 | urlpatterns = patterns('',
9 | # Examples:
10 | # url(r'^$', 'v1.views.home', name='home'),
11 | # url(r'^v1/', include('v1.foo.urls')),
12 |
13 | # Uncomment the admin/doc line below to enable admin documentation:
14 | # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
15 |
16 | # Uncomment the next line to enable the admin:
17 | # url(r'^admin/', include(admin.site.urls)),
18 | )
19 |
--------------------------------------------------------------------------------
/tests/multiple_database/fixtures/multidb.default.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "pk": 1,
4 | "model": "multiple_database.person",
5 | "fields": {
6 | "name": "Marty Alchin"
7 | }
8 | },
9 | {
10 | "pk": 2,
11 | "model": "multiple_database.person",
12 | "fields": {
13 | "name": "George Vilches"
14 | }
15 | },
16 | {
17 | "pk": 2,
18 | "model": "multiple_database.book",
19 | "fields": {
20 | "title": "Pro Django",
21 | "published": "2008-12-16",
22 | "editor": ["George Vilches"]
23 | }
24 | }
25 | ]
26 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v2/nonrelblog/tests.py:
--------------------------------------------------------------------------------
1 | __test__ = {
2 | 'v4': """
3 | >>> from nonrelblog.models import Post
4 | >>> from nonrelblog.models import Comment, Author
5 | >>> Comment(
6 | ... author=Author(name='Bob', email='bob@example.org'),
7 | ... text='The cake is a lie'
8 | ... ).save()
9 | >>> comment = Comment.objects.get()
10 | >>> comment.author
11 |
12 | >>> Post(
13 | ... title='I like cake',
14 | ... comments=[comment]
15 | ... ).save()
16 | >>> post = Post.objects.get(title='I like cake')
17 | >>> post.comments
18 | []
19 | >>> post.comments[0].author.email
20 | u'bob@example.org'
21 | """}
22 |
--------------------------------------------------------------------------------
/django_mongodb_engine/models.py:
--------------------------------------------------------------------------------
1 | # If you wonder what this file is about please head over to '__init__.py' :-)
2 |
3 | from django.db.models import signals
4 |
5 |
6 | def class_prepared_mongodb_signal(sender, *args, **kwargs):
7 | mongo_meta = getattr(sender, 'MongoMeta', None)
8 | if mongo_meta is not None:
9 | for attr in dir(mongo_meta):
10 | if not attr.startswith('_'):
11 | if attr == 'index_together':
12 | attr_name = 'mongo_index_together'
13 | else:
14 | attr_name = attr
15 | setattr(sender._meta, attr_name, getattr(mongo_meta, attr))
16 |
17 | signals.class_prepared.connect(class_prepared_mongodb_signal)
18 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v4/gridfsuploads/views.py:
--------------------------------------------------------------------------------
1 | from mimetypes import guess_type
2 |
3 | from django.conf import settings
4 | from django.http import HttpResponse, Http404
5 |
6 | from gridfs.errors import NoFile
7 | from gridfsuploads import gridfs_storage
8 | from gridfsuploads.models import FileUpload
9 |
10 |
11 | if settings.DEBUG:
12 |
13 | def serve_from_gridfs(request, path):
14 | # Serving GridFS files through Django is inefficient and
15 | # insecure. NEVER USE IN PRODUCTION!
16 | try:
17 | gridfile = gridfs_storage.open(path)
18 | except NoFile:
19 | raise Http404
20 | else:
21 | return HttpResponse(gridfile, mimetype=guess_type(path)[0])
22 |
--------------------------------------------------------------------------------
/docs/source/doc-organization.rst:
--------------------------------------------------------------------------------
1 | * :doc:`The tutorial ` introduces you to non-relational schema design
2 | and tools provided by Django MongoDB Engine to put that design into action.
3 | * Our :doc:`topical guides ` explain every feature of
4 | Django MongoDB Engine in great detail -- they'll provide you with usage guides,
5 | API overviews, code samples etc.
6 | * The :doc:`API reference ` has all the lower-level details on
7 | functions, methods, classes, settings, modules and whatnot.
8 | * In the :doc:`Meta section `, we collect information about the
9 | project itself: :doc:`What has changed between versions `,
10 | how development happens and :doc:`how you can get involved `.
11 |
--------------------------------------------------------------------------------
/django_mongodb_engine/management/commands/tellsiteid.py:
--------------------------------------------------------------------------------
1 | from django.core.management.base import NoArgsCommand
2 |
3 |
4 | class Command(NoArgsCommand):
5 | help = "Tells the ID of the default Site object."
6 |
7 | def handle_noargs(self, **options):
8 | verbosity = int(options.get('verbosity', 1))
9 | site_id = self._get_site_id()
10 | if verbosity >= 1:
11 | self.stdout.write(
12 | "The default site's ID is %r. To use the sites framework, "
13 | "add this line to settings.py:\nSITE_ID=%r" %
14 | (site_id, site_id))
15 | else:
16 | self.stdout.write(site_id)
17 |
18 | def _get_site_id(self):
19 | from django.contrib.sites.models import Site
20 | return Site.objects.get().id
21 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v2/nonrelblog/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | from djangotoolbox.fields import ListField, EmbeddedModelField
4 |
5 |
6 | class Post(models.Model):
7 | created_on = models.DateTimeField(auto_now_add=True, null=True)
8 | title = models.CharField()
9 | text = models.TextField()
10 | tags = ListField()
11 | comments = ListField(EmbeddedModelField('Comment')) # <---
12 |
13 |
14 | class Comment(models.Model):
15 | created_on = models.DateTimeField(auto_now_add=True)
16 | author = EmbeddedModelField('Author')
17 | text = models.TextField()
18 |
19 |
20 | class Author(models.Model):
21 | name = models.CharField()
22 | email = models.EmailField()
23 |
24 | def __unicode__(self):
25 | return '%s (%s)' % (self.name, self.email)
26 |
--------------------------------------------------------------------------------
/django_mongodb_engine/query.py:
--------------------------------------------------------------------------------
1 | from warnings import warn
2 |
3 | from djangotoolbox.fields import RawField, AbstractIterableField, \
4 | EmbeddedModelField
5 |
6 |
7 | __all__ = ['A']
8 |
9 |
10 | DJANGOTOOLBOX_FIELDS = (RawField, AbstractIterableField, EmbeddedModelField)
11 |
12 |
13 | class A(object):
14 |
15 | def __init__(self, op, value):
16 | warn("A() queries are deprecated as of 0.5 and will be removed in 0.6.", DeprecationWarning)
17 |
18 | self.op = op
19 | self.val = value
20 |
21 | def as_q(self, field):
22 | if isinstance(field, DJANGOTOOLBOX_FIELDS):
23 | return '%s.%s' % (field.column, self.op), self.val
24 | else:
25 | raise TypeError("Can not use A() queries on %s." %
26 | field.__class__.__name__)
27 |
--------------------------------------------------------------------------------
/tests/contrib/models.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.db import models
3 | from django.utils.translation import ugettext_lazy as _
4 |
5 | from django_mongodb_engine.contrib import MongoDBManager
6 | from django_mongodb_engine.contrib.search.fields import TokenizedField
7 |
8 |
9 | class MapReduceModel(models.Model):
10 | n = models.IntegerField()
11 | m = models.IntegerField()
12 |
13 | objects = MongoDBManager()
14 |
15 |
16 | class MapReduceModelWithCustomPrimaryKey(models.Model):
17 | primarykey = models.CharField(max_length=100, primary_key=True)
18 | data = models.CharField(max_length=100)
19 | objects = MongoDBManager()
20 |
21 |
22 | class Post(models.Model):
23 | content = TokenizedField(max_length=255)
24 |
25 | def __unicode__(self):
26 | return "Post"
27 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v3/nonrelblog/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | from djangotoolbox.fields import ListField, EmbeddedModelField
4 |
5 |
6 | class Post(models.Model):
7 | created_on = models.DateTimeField(auto_now_add=True, null=True)
8 | title = models.CharField(max_length=100)
9 | text = models.TextField()
10 | tags = ListField()
11 | comments = ListField(EmbeddedModelField('Comment')) # <---
12 |
13 |
14 | class Comment(models.Model):
15 | created_on = models.DateTimeField(auto_now_add=True)
16 | author = EmbeddedModelField('Author')
17 | text = models.TextField()
18 |
19 |
20 | class Author(models.Model):
21 | name = models.CharField(max_length=50)
22 | email = models.EmailField()
23 |
24 | def __unicode__(self):
25 | return '%s (%s)' % (self.name, self.email)
26 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v4/nonrelblog/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | from djangotoolbox.fields import ListField, EmbeddedModelField
4 |
5 |
6 | class Post(models.Model):
7 | created_on = models.DateTimeField(auto_now_add=True, null=True)
8 | title = models.CharField(max_length=100)
9 | text = models.TextField()
10 | tags = ListField()
11 | comments = ListField(EmbeddedModelField('Comment')) # <---
12 |
13 |
14 | class Comment(models.Model):
15 | created_on = models.DateTimeField(auto_now_add=True)
16 | author = EmbeddedModelField('Author')
17 | text = models.TextField()
18 |
19 |
20 | class Author(models.Model):
21 | name = models.CharField(max_length=50)
22 | email = models.EmailField()
23 |
24 | def __unicode__(self):
25 | return '%s (%s)' % (self.name, self.email)
26 |
--------------------------------------------------------------------------------
/docs/source/mongodbtheme/layout.html:
--------------------------------------------------------------------------------
1 | {# Hack to get rid of jQuery and friends #}
2 | {% set script_files = [] %}
3 |
4 | {% extends 'basic/layout.html' %}
5 |
6 | {# No relbars please #}
7 | {% block relbar1 %}{% endblock %}
8 | {% block relbar2 %}{% endblock %}
9 |
10 | {%- block header %}
11 |
18 | {%- endblock %}
19 |
20 | {# Sidebar before content #}
21 | {%- block sidebar1 %}
22 |
23 | {{ sidebar() }}
24 | {% endblock %}
25 | {%- block sidebar2 %}{% endblock %}
26 |
27 | {# Wrap all content to make special-casing certain pages possible #}
28 | {%- block content %}
29 |
30 | {{ super() }}
31 |
32 | {%- endblock %}
33 |
--------------------------------------------------------------------------------
/tests/lookup/models.py:
--------------------------------------------------------------------------------
1 | """
2 | 7. The lookup API
3 |
4 | This demonstrates features of the database API.
5 | """
6 |
7 | from django.conf import settings
8 | from django.db import models, DEFAULT_DB_ALIAS, connection
9 |
10 |
11 | class Author(models.Model):
12 | name = models.CharField(max_length=100)
13 |
14 | class Meta:
15 | ordering = ('name', )
16 |
17 |
18 | class Article(models.Model):
19 | headline = models.CharField(max_length=100)
20 | pub_date = models.DateTimeField()
21 | author = models.ForeignKey(Author, blank=True, null=True)
22 |
23 | class Meta:
24 | ordering = ('-pub_date', 'headline')
25 |
26 | def __unicode__(self):
27 | return self.headline
28 |
29 |
30 | class Tag(models.Model):
31 | articles = models.ManyToManyField(Article)
32 | name = models.CharField(max_length=100)
33 |
34 | class Meta:
35 | ordering = ('name', )
36 |
--------------------------------------------------------------------------------
/AUTHORS.rst:
--------------------------------------------------------------------------------
1 | Authors
2 | =======
3 |
4 | Current Primary Authors
5 | -----------------------
6 | * Jonas Haag
7 | * Flavio Percoco Premoli
8 | * Wojtek Ruszczewski
9 |
10 | Past Primary Authors
11 | --------------------
12 | * Alberto Paro
13 | * George Karpenkov
14 |
15 | Contributions by
16 | ----------------
17 | * Ellie Frost (https://github.com/stillinbeta)
18 | * Simon Charette (https://github.com/charettes)
19 | * Dag Stockstad (https://github.com/dstockstad) -- found tons of bugs in our query generator
20 | * Shane R. Spencer (https://github.com/whardier) -- Website Review
21 | * Sabin Iacob (https://github.com/m0n5t3r)
22 | * kryton (https://github.com/kryton)
23 | * Brandon Pedersen (https://github.com/bpedman)
24 |
25 | (For an up-to-date list of contributors, see
26 | https://github.com/django-nonrel/mongodb-engine/contributors.)
27 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v7/nonrelblog/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | from djangotoolbox.fields import ListField, EmbeddedModelField
4 |
5 | from django_mongodb_engine.contrib import MongoDBManager
6 |
7 |
8 | class Post(models.Model):
9 | created_on = models.DateTimeField(auto_now_add=True, null=True)
10 | title = models.CharField(max_length=100)
11 | text = models.TextField()
12 | tags = ListField()
13 | comments = ListField(EmbeddedModelField('Comment')) # <---
14 |
15 | objects = MongoDBManager()
16 |
17 |
18 | class Comment(models.Model):
19 | created_on = models.DateTimeField(auto_now_add=True)
20 | author = EmbeddedModelField('Author')
21 | text = models.TextField()
22 |
23 |
24 | class Author(models.Model):
25 | name = models.CharField(max_length=50)
26 | email = models.EmailField()
27 |
28 | def __unicode__(self):
29 | return '%s (%s)' % (self.name, self.email)
30 |
--------------------------------------------------------------------------------
/docs/source/reference/lowerlevel.rst:
--------------------------------------------------------------------------------
1 | .. the following warning is a 1:1 copy from topics/lowerlevel.rst
2 | .. warning::
3 |
4 | These APIs are available for MongoDB only, so using any of these features
5 | breaks portability to other non-relational databases (Google App Engine,
6 | Cassandra, Redis, ...). For the sake of portability you should try to avoid
7 | database-specific features whenever possible.
8 |
9 | Lower-Level API
10 | ===============
11 | ::
12 |
13 | from django_mongodb_engine.contrib import MongoDBManager
14 |
15 | class FooModel(models.Model):
16 | ...
17 | objects = MongoDBManager()
18 |
19 | ::
20 |
21 | >>> FooModel.objects.raw_query(...)
22 | >>> FooModel.objects.raw_update(...)
23 |
24 | .. currentmodule:: django_mongodb_engine.contrib
25 |
26 | .. automethod:: MongoDBManager.raw_query
27 |
28 | .. automethod:: MongoDBManager.raw_update
29 |
30 | .. automethod:: MongoDBManager.distinct
31 |
--------------------------------------------------------------------------------
/tests/settings/serializer.py:
--------------------------------------------------------------------------------
1 | from django_mongodb_engine.creation import DatabaseCreation
2 | from django.core.serializers.json import \
3 | Serializer, Deserializer as JSONDeserializer
4 |
5 |
6 | def get_objectid_fields(modelopts, typemap=DatabaseCreation.data_types):
7 | return [field for field in modelopts.fields if
8 | typemap.get(field.__class__.__name__) == 'key']
9 |
10 |
11 | def Deserializer(*args, **kwargs):
12 | for objwrapper in JSONDeserializer(*args, **kwargs):
13 | obj = objwrapper.object
14 | for field in get_objectid_fields(obj._meta):
15 | value = getattr(obj, field.attname)
16 | try:
17 | int(value)
18 | except (TypeError, ValueError):
19 | pass
20 | else:
21 | setattr(obj, field.attname, int_to_objectid(value))
22 | yield objwrapper
23 |
24 |
25 | def int_to_objectid(i):
26 | return str(i).rjust(24, '0')
27 |
--------------------------------------------------------------------------------
/tests/embedded/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | from djangotoolbox.fields import DictField, EmbeddedModelField
4 |
5 |
6 | class EmbeddedModel(models.Model):
7 | charfield = models.CharField(max_length=3, blank=False)
8 | datetime = models.DateTimeField(null=True)
9 | datetime_auto_now_add = models.DateTimeField(auto_now_add=True)
10 | datetime_auto_now = models.DateTimeField(auto_now=True)
11 |
12 |
13 | class Model(models.Model):
14 | x = models.IntegerField()
15 | em = EmbeddedModelField(EmbeddedModel)
16 | dict_emb = DictField(EmbeddedModelField(EmbeddedModel))
17 |
18 |
19 | # Docstring example copy.
20 | class Address(models.Model):
21 | street = models.CharField(max_length=200)
22 | postal_code = models.IntegerField()
23 | city = models.CharField(max_length=100)
24 |
25 |
26 | class Customer(models.Model):
27 | name = models.CharField(max_length=100)
28 | last_name = models.CharField(max_length=100)
29 | address = EmbeddedModelField(Address)
30 |
--------------------------------------------------------------------------------
/docs/source/topics/atomic-updates.rst:
--------------------------------------------------------------------------------
1 | Atomic Updates
2 | ==============
3 |
4 | Django's support for updates_
5 | (using the :meth:`~django.db.models.query.QuerySet.update` method)
6 | can be used to run atomic updates against a single or multiple documents::
7 |
8 | Post.objects.filter(...).update(title='Everything is the same')
9 |
10 | results in a :meth:`~pymongo.collection.Collection.update` query that uses the
11 | atomic ``$set`` operator to update the `title` field::
12 |
13 | .update(..., {'$set': {'title': 'Everything is the same'}})
14 |
15 | It's also possible to use `F()`_ objects which are
16 | translated into ``$inc`` operations. For example, ::
17 |
18 | Post.objects.filter(...).update(visits=F('visits')+1)
19 |
20 | is translated to::
21 |
22 | .update(..., {'$inc': {'visits': 1}})
23 |
24 | .. _updates: https://docs.djangoproject.com/en/dev/topics/db/queries/#updating-multiple-objects-at-once
25 | .. _F(): https://docs.djangoproject.com/en/dev/topics/db/queries/#filters-can-reference-fields-on-the-model
26 |
--------------------------------------------------------------------------------
/docs/source/reference/fields.rst:
--------------------------------------------------------------------------------
1 | Fields
2 | ======
3 |
4 | This is a reference of both fields that are implemented in djangotoolbox_ and
5 | fields specific to MongoDB.
6 |
7 | (In signatures, ``...`` represents arbitrary positional and keyword arguments
8 | that are passed to :class:`django.db.models.Field`.)
9 |
10 | in :mod:`djangotoolbox`
11 | ---------------------------
12 | .. currentmodule:: djangotoolbox.fields
13 |
14 | .. autoclass:: ListField(item_field=None, ...)
15 |
16 | .. autoclass:: SetField(item_field=None, ...)
17 |
18 | .. autoclass:: DictField(item_field=None, ...)
19 |
20 | .. autoclass:: EmbeddedModelField(embedded_model=None, ...)
21 |
22 | .. autoclass:: BlobField(...)
23 |
24 | in :mod:`django_mongodb_engine`
25 | -----------------------------------
26 | .. currentmodule:: django_mongodb_engine.fields
27 |
28 | .. autoclass:: GridFSField(delete=True, versioning=False, ...)
29 |
30 | .. autoclass:: GridFSString(delete=True, versioning=False, ...)
31 |
32 |
33 | .. _djangotoolbox: http://allbuttonspressed.com/projects/djangotoolbox
34 |
--------------------------------------------------------------------------------
/docs/source/code/mapreduce/mr/tests.py:
--------------------------------------------------------------------------------
1 | mapfunc = """
2 | function() {
3 | this.text.split(' ').forEach(
4 | function(word) { emit(word, 1) }
5 | )
6 | }
7 | """
8 |
9 | reducefunc = """
10 | function reduce(key, values) {
11 | return values.length; /* == sum(values) */
12 | }
13 | """
14 |
15 | __test__ = {
16 | 'mr': """
17 | >>> from models import Author, Article
18 |
19 | >>> bob = Author.objects.create()
20 | >>> ann = Author.objects.create()
21 |
22 | >>> bobs_article = Article.objects.create(author=bob, text="A B C")
23 | >>> anns_article = Article.objects.create(author=ann, text="A B C D E")
24 |
25 | Map/Reduce over all articles:
26 | >>> for pair in Article.objects.map_reduce(mapfunc, reducefunc, 'wordcount'):
27 | ... print pair.key, pair.value
28 | A 2.0
29 | B 2.0
30 | C 2.0
31 | D 1.0
32 | E 1.0
33 |
34 | Map/Reduce over Bob's articles:
35 | >>> for pair in Article.objects.filter(author=bob).map_reduce(
36 | mapfunc, reducefunc, 'wordcount'):
37 | ... print pair.key, pair.value
38 | A 1.0
39 | B 1.0
40 | C 1.0
41 | """}
42 |
--------------------------------------------------------------------------------
/docs/source/topics/aggregations.rst:
--------------------------------------------------------------------------------
1 | Aggregations
2 | ============
3 |
4 | Django has `out-of-the-box support for aggregation`__.
5 | The following aggregations are currently supported by Django MongoDB Engine:
6 |
7 | * :class:`~django.db.models.Count`
8 | * :class:`~django.db.models.Avg`
9 | * :class:`~django.db.models.Min`
10 | * :class:`~django.db.models.Max`
11 | * :class:`~django.db.models.Sum`
12 |
13 | MongoDB's group_ command is used to perform aggregations using generated
14 | Javascript code that implements the aggregation functions.
15 |
16 | While being more flexible than :doc:`mapreduce`, a ``group`` command can not be
17 | processed in parallel, for which reason you should prefer Map/Reduce to process
18 | big data sets.
19 |
20 | .. warning::
21 |
22 | Needless to say, you shouldn't use these aggregations on a regular basis
23 | (i.e. in your views or business logic) but regard them as a powerful tool for
24 | one-time operations.
25 |
26 | .. _group: http://docs.mongodb.org/manual/reference/command/group/#dbcmd.group
27 | .. __: https://docs.djangoproject.com/en/dev/topics/db/aggregation/
28 |
--------------------------------------------------------------------------------
/tests/utils.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.db import connections
3 | from django.db.models import Model
4 | from django.test import TestCase
5 | from django.utils.unittest import skip
6 |
7 | class TestCase(TestCase):
8 |
9 | def setUp(self):
10 | super(TestCase, self).setUp()
11 | if getattr(settings, 'TEST_DEBUG', False):
12 | settings.DEBUG = True
13 |
14 | def assertEqualLists(self, a, b):
15 | self.assertEqual(list(a), list(b))
16 |
17 |
18 | def skip_all_except(*tests):
19 |
20 | class meta(type):
21 |
22 | def __new__(cls, name, bases, dict):
23 | for attr in dict.keys():
24 | if attr.startswith('test_') and attr not in tests:
25 | del dict[attr]
26 | return type.__new__(cls, name, bases, dict)
27 |
28 | return meta
29 |
30 |
31 | def get_collection(model_or_name):
32 | if isinstance(model_or_name, type) and issubclass(model_or_name, Model):
33 | model_or_name = model_or_name._meta.db_table
34 | return connections['default'].get_collection(model_or_name)
35 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | Django MongoDB Engine, the MongoDB backend for Django
2 | =====================================================
3 |
4 | Documentation on http://django-mongodb-engine.readthedocs.org/
5 |
6 | *Use Django's ORM (including Aggregations, Atomic Updates, Embedded Objects,
7 | Map/Reduce and GridFS), admin site, authentication, site, session and caching
8 | frameworks with MongoDB.*
9 |
10 | :master branch:
11 | .. image:: https://secure.travis-ci.org/django-nonrel/mongodb-engine.png?branch=master
12 | :target: https://travis-ci.org/django-nonrel/mongodb-engine
13 |
14 |
15 | Contributing
16 | ------------
17 | You are highly encouraged to participate in the development, simply use
18 | GitHub's fork/pull request system.
19 | If you don't like GitHub (for some reason) you're welcome
20 | to send regular patches to the mailing list.
21 |
22 | :Web site: http://www.django-nonrel.org/
23 | :Mailing list: http://groups.google.com/group/django-non-relational
24 | :Bug tracker: https://github.com/django-nonrel/mongodb-engine/issues/
25 | :PyPI: http://pypi.python.org/pypi/django-mongodb-engine/
26 | :License: 2-clause BSD
27 | :Keywords: django, mongodb, orm, nosql, database, python
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | branches:
2 | only:
3 | - master
4 |
5 | language: python
6 |
7 | services:
8 | - mongodb
9 |
10 | python:
11 | - 2.6
12 | - 2.7
13 |
14 | env:
15 | - DJANGO_VERSION=1.4 PYMONGO_VERSION=2.8.1
16 | - DJANGO_VERSION=1.4 PYMONGO_VERSION=3.0.2
17 | - DJANGO_VERSION=1.5 PYMONGO_VERSION=2.8.1
18 | - DJANGO_VERSION=1.5 PYMONGO_VERSION=3.0.2
19 | - DJANGO_VERSION=1.6 PYMONGO_VERSION=2.8.1
20 | - DJANGO_VERSION=1.6 PYMONGO_VERSION=3.0.2
21 | - DJANGO_VERSION=1.7 PYMONGO_VERSION=2.8.1
22 | - DJANGO_VERSION=1.7 PYMONGO_VERSION=3.0.2
23 |
24 |
25 | matrix:
26 | allow_failures:
27 | - env: DJANGO_VERSION=1.4 PYMONGO_VERSION=2.8.1
28 | - env: DJANGO_VERSION=1.4 PYMONGO_VERSION=3.0.2
29 | - env: DJANGO_VERSION=1.7 PYMONGO_VERSION=2.8.1
30 | - env: DJANGO_VERSION=1.7 PYMONGO_VERSION=3.0.2
31 |
32 | install:
33 | - pip install git+http://github.com/django-nonrel/django@nonrel-$DJANGO_VERSION
34 | - pip install git+http://github.com/django-nonrel/django-dbindexer@master
35 | - pip install git+http://github.com/django-nonrel/djangotoolbox@master
36 | - pip install pymongo==$PYMONGO_VERSION --upgrade
37 | - python setup.py install
38 |
39 | script: cd tests && python runtests.py
40 |
--------------------------------------------------------------------------------
/tests/aggregations/tests.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 |
3 | from django.db.models.aggregates import Count, Sum, Max, Min, Avg
4 |
5 | from .utils import TestCase
6 | from models import Person
7 |
8 |
9 | class SimpleTest(TestCase):
10 |
11 | def test_aggregations(self):
12 | for age, birthday in (
13 | [4, (2007, 12, 25)],
14 | [4, (2006, 1, 1)],
15 | [1, (2008, 12, 1)],
16 | [4, (2006, 6, 1)],
17 | [12, (1998, 9, 1)],
18 | ):
19 | Person.objects.create(age=age, birthday=datetime(*birthday))
20 |
21 | aggregates = Person.objects.aggregate(Min('age'), Max('age'),
22 | avgage=Avg('age'))
23 | self.assertEqual(aggregates, {'age__min': 1, 'age__max': 12,
24 | 'avgage': 5.0})
25 |
26 | # With filters and testing the sqlaggregates->mongoaggregate
27 | # conversion.
28 | aggregates = Person.objects.filter(age__gte=4).aggregate(
29 | Min('birthday'), Max('birthday'), Avg('age'), Count('id'))
30 | self.assertEqual(aggregates, {
31 | 'birthday__max': datetime(2007, 12, 25, 0, 0),
32 | 'birthday__min': datetime(1998, 9, 1, 0, 0),
33 | 'age__avg': 6.0,
34 | 'id__count': 4,
35 | })
36 |
--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | import sys; sys.path.append('.')
3 | from .utils import get_current_year, get_git_head
4 |
5 | project = 'Django MongoDB Engine'
6 | copyright = '2010-%d, Jonas Haag, Flavio Percoco Premoli and contributors' % \
7 | get_current_year()
8 |
9 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx']
10 |
11 | master_doc = 'index'
12 | exclude_patterns = ['_build']
13 |
14 | pygments_style = 'friendly'
15 |
16 | intersphinx_mapping = {
17 | 'python': ('http://docs.python.org', None),
18 | 'pymongo': ('http://api.mongodb.org/python/current/', None),
19 | 'django': ('http://docs.djangoproject.com/en/dev/',
20 | 'http://docs.djangoproject.com/en/dev/_objects/'),
21 | }
22 |
23 | # -- Options for HTML output ---------------------------------------------------
24 |
25 | html_title = project
26 |
27 | html_last_updated_fmt = '%b %d, %Y'
28 | git_head = get_git_head()
29 | if git_head:
30 | html_last_updated_fmt += ' (%s)' % git_head[:7]
31 |
32 | html_theme = 'mongodbtheme'
33 | html_theme_path = ['mongodbtheme', '.']
34 | html_show_copyright = False
35 |
36 | # Custom sidebar templates, maps document names to template names.
37 | html_sidebars = {'**': ['localtoc.html', 'sidebar.html']}
38 |
39 | # If false, no module index is generated.
40 | html_domain_indices = False
41 |
42 | # If false, no index is generated.
43 | html_use_index = False
44 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 | import django_mongodb_engine as distmeta
4 |
5 | DESCRIPTION = 'MongoDB backend for Django-nonrel'
6 | LONG_DESCRIPTION = None
7 | try:
8 | LONG_DESCRIPTION = open('README.rst').read()
9 | except:
10 | pass
11 |
12 | setup(name='django-mongodb-engine',
13 | version='.'.join(map(str, distmeta.__version__)),
14 | author=distmeta.__author__,
15 | author_email=distmeta.__contact__,
16 | url=distmeta.__homepage__,
17 | license='2-clause BSD',
18 | description=DESCRIPTION,
19 | long_description=LONG_DESCRIPTION,
20 |
21 | install_requires=['pymongo>=2.8', 'djangotoolbox>=1.6.0'],
22 |
23 | packages=find_packages(exclude=['tests', 'tests.*']),
24 | zip_safe=False,
25 | classifiers=[
26 | 'Development Status :: 5 - Production/Stable',
27 | 'Environment :: Web Environment',
28 | 'Framework :: Django',
29 | 'Intended Audience :: Developers',
30 | 'License :: OSI Approved :: BSD License',
31 | 'Operating System :: OS Independent',
32 | 'Programming Language :: Python',
33 | 'Programming Language :: Python :: 2.5',
34 | 'Programming Language :: Python :: 2.6',
35 | 'Programming Language :: Python :: 2.7',
36 | 'Topic :: Database',
37 | 'Topic :: Internet',
38 | 'Topic :: Software Development :: Libraries :: Python Modules',
39 | ],
40 | )
41 |
--------------------------------------------------------------------------------
/django_mongodb_engine/contrib/search/tokenizer.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 |
4 | class BaseTokenizer(object):
5 | """
6 | Really simple tokenizer.
7 | """
8 |
9 | @staticmethod
10 | def tokenize(text):
11 | """
12 | Splits text into a list of words removing any symbol and
13 | converts it into lowercase.
14 | """
15 | tokens = []
16 | text = text.lower()
17 | for dot_item in BaseTokenizer.regex_split('\.(?=[a-zA-Z\s])', text):
18 | for comman_item in BaseTokenizer.regex_split(',(?=[a-zA-Z\s])',
19 | dot_item):
20 | for item in comman_item.split(' '):
21 | item = BaseTokenizer.tokenize_item(item)
22 | if item:
23 | tokens.append(item)
24 | return tokens
25 |
26 | @staticmethod
27 | def regex_split(regex, text):
28 | for item in re.split(regex, text, re.I):
29 | yield item
30 |
31 | @staticmethod
32 | def tokenize_item(item):
33 | """
34 | If it is an int/float it returns the item (there's no need to
35 | remove , or .).
36 | """
37 | item = item.strip()
38 | try:
39 | float(item)
40 | return item
41 | except ValueError:
42 | pass
43 |
44 | # This will keep underscores.
45 | return re.sub(r'[^\w]', '', item)
46 |
--------------------------------------------------------------------------------
/tests/router/tests.py:
--------------------------------------------------------------------------------
1 | from django.db.utils import DatabaseError
2 | from django.test import TestCase
3 |
4 |
5 | class RouterTest(TestCase):
6 |
7 | def test_managed_apps(self):
8 | # MONGODB_MANAGED_APPS = ['query'] : Any 'query' model resides
9 | # in the MongoDB 'other'.
10 | from query.models import Blog
11 | Blog.objects.create()
12 | self.assertEqual(Blog.objects.using('other').count(), 1)
13 | self.assertRaisesRegexp(DatabaseError, "no such table",
14 | Blog.objects.using('default').count)
15 |
16 | def test_managed_models(self):
17 | # MONGODB_MANAGED_MODELS = ['router.MongoDBModel']:
18 | # router.models.MongoDBModel resides in MongoDB,
19 | # .SQLiteModel in SQLite.
20 | from router.models import MongoDBModel, SQLiteModel
21 | mongo_obj = MongoDBModel.objects.create()
22 | sql_obj = SQLiteModel.objects.create()
23 |
24 | self.assertEqual(MongoDBModel.objects.get(), mongo_obj)
25 | self.assertEqual(SQLiteModel.objects.get(), sql_obj)
26 |
27 | self.assertEqual(MongoDBModel.objects.using('other').get(), mongo_obj)
28 | self.assertEqual(SQLiteModel.objects.using('default').get(), sql_obj)
29 |
30 | self.assertEqual(SQLiteModel.objects.using('other').count(), 0)
31 | self.assertRaisesRegexp(DatabaseError, "no such table",
32 | MongoDBModel.objects.using('default').count)
33 |
--------------------------------------------------------------------------------
/docs/source/code/tutorial/v7/nonrelblog/tests.py:
--------------------------------------------------------------------------------
1 | mapfunc = """
2 | function map() {
3 | /* `this` refers to the current document */
4 | this.comments.forEach(function(comment) {
5 | emit(comment.author.name, 1);
6 | });
7 | }
8 | """
9 |
10 | reducefunc = """
11 | function reduce(id, values) {
12 | /* [1, 1, ..., 1].length is the same as sum([1, 1, ..., 1]) */
13 | return values.length;
14 | }
15 | """
16 |
17 | __test__ = {'mapreduce': """
18 | >>> from nonrelblog.models import *
19 |
20 | Add some data so we can actually mapreduce anything.
21 | Bob: 3 comments
22 | Ann: 6 comments
23 | Alice: 9 comments
24 | >>> authors = [Author(name='Bob', email='bob@example.org'),
25 | ... Author(name='Ann', email='ann@example.org'),
26 | ... Author(name='Alice', email='alice@example.org')]
27 | >>> for distribution in [(0, 1, 2), (1, 2, 3), (2, 3, 4)]:
28 | ... comments = []
29 | ... for author, ncomments in zip(authors, distribution):
30 | ... comments.extend([Comment(author=author)
31 | ... for i in xrange(ncomments)])
32 | ... Post(comments=comments).save()
33 |
34 | ------------------------
35 | Kick off the Map/Reduce:
36 | ------------------------
37 | >>> pairs = Post.objects.map_reduce(mapfunc, reducefunc, out='temp',
38 | ... delete_collection=True)
39 | >>> for pair in pairs:
40 | ... print pair.key, pair.value
41 | Alice 9.0
42 | Ann 6.0
43 | Bob 3.0
44 | """}
45 |
--------------------------------------------------------------------------------
/tests/query/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.conf import settings
3 |
4 | from djangotoolbox.fields import ListField, RawField
5 |
6 |
7 | class RawModel(models.Model):
8 | raw = RawField()
9 |
10 |
11 | class Empty(models.Model):
12 | pass
13 |
14 |
15 | class IntegerModel(models.Model):
16 | integer = models.IntegerField()
17 |
18 |
19 | class Blog(models.Model):
20 | title = models.CharField(max_length=200, db_index=True)
21 |
22 | class Meta:
23 | ordering = ['id']
24 |
25 |
26 | class Post(models.Model):
27 | title = models.CharField(max_length=200, db_index=True, unique=True)
28 | content = models.CharField(max_length=1000, db_column='text')
29 | date_published = models.DateTimeField(null=True, blank=True)
30 | blog = models.ForeignKey(Blog, null=True, blank=True)
31 |
32 |
33 | # TODO: Get rid of this model.
34 | class Person(models.Model):
35 | name = models.CharField(max_length=20)
36 | surname = models.CharField(max_length=20)
37 | age = models.IntegerField(null=True, blank=True)
38 | another_age = models.IntegerField(null=True, blank=True, db_column='age2')
39 |
40 | class Meta:
41 | unique_together = ('name', 'surname')
42 |
43 |
44 | class DateModel(models.Model):
45 | datetime = models.DateTimeField(auto_now_add=True)
46 | time = models.TimeField(null=True)
47 | date = models.DateField(null=True)
48 | _datelist_default = []
49 | datelist = ListField(models.DateField(), default=_datelist_default)
50 |
51 |
52 | class Article(models.Model):
53 | headline = models.CharField(max_length=50)
54 | pub_date = models.DateTimeField()
55 |
--------------------------------------------------------------------------------
/django_mongodb_engine/contrib/search/fields.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | from .tokenizer import BaseTokenizer
4 |
5 |
6 | __all__ = ['TokenizedField']
7 |
8 |
9 | class TokenizedField(models.Field):
10 |
11 | def __init__(self, *args, **kwargs):
12 | super(TokenizedField, self).__init__(*args, **kwargs)
13 | as_textfield = kwargs.pop('as_textfield', False)
14 | self._tokenizer = kwargs.pop('tokenizer', BaseTokenizer)()
15 | self.parent_field = models.CharField(*args, **kwargs)
16 |
17 | def contribute_to_class(self, cls, name):
18 | super(TokenizedField, self).contribute_to_class(
19 | cls, '%s_tokenized' % name)
20 | setattr(self, 'parent_field_name', name)
21 | cls.add_to_class(name, self.parent_field)
22 |
23 | def get_db_prep_lookup(self, lookup_type, value, connection,
24 | prepared=False):
25 | # If for some reason value is being converted to list by some
26 | # internal processing we'll convert it back to string.
27 | # For Example: When using the 'in' lookup type.
28 | if isinstance(value, list):
29 | value = ''.join(value)
30 |
31 | # When 'exact' is used we'll perform an exact_phrase query
32 | # using the $all operator otherwhise we'll just tokenized
33 | # the value. Djangotoolbox will do the remaining checks.
34 | if lookup_type == 'exact':
35 | return {'$all': self._tokenizer.tokenize(value)}
36 | return self._tokenizer.tokenize(value)
37 |
38 | def pre_save(self, model_instance, add):
39 | return self._tokenizer.tokenize(getattr(model_instance,
40 | self.parent_field_name))
41 |
--------------------------------------------------------------------------------
/django_mongodb_engine/aggregations.py:
--------------------------------------------------------------------------------
1 | class MongoAggregate(object):
2 | is_ordinal = False
3 | is_computed = False
4 | reduce_template = NotImplemented
5 | finalize_template = ''
6 |
7 | def __init__(self, alias, lookup, source):
8 | self.alias = alias
9 | self.lookup = lookup
10 | self.field = self.source = source
11 |
12 | def format(self, template):
13 | alias = 'out.%s' % self.alias
14 | lookup = 'doc.%s' % self.lookup
15 | return template.format(alias=alias, lookup=lookup)
16 |
17 | def initial(self):
18 | return {self.alias: self.initial_value}
19 |
20 | def reduce(self):
21 | return self.format(self.reduce_template)
22 |
23 | def finalize(self):
24 | return self.format(self.finalize_template)
25 |
26 | def as_sql(self):
27 | raise NotImplementedError
28 |
29 |
30 | class Count(MongoAggregate):
31 | is_ordinal = True
32 | initial_value = 0
33 | reduce_template = '{alias}++'
34 |
35 |
36 | class Min(MongoAggregate):
37 | initial_value = float('inf')
38 | reduce_template = '{alias} = ({lookup} < {alias}) ? {lookup}: {alias}'
39 |
40 |
41 | class Max(MongoAggregate):
42 | initial_value = float('-inf')
43 | reduce_template = '{alias} = ({lookup} > {alias}) ? {lookup}: {alias}'
44 |
45 |
46 | class Avg(MongoAggregate):
47 | is_computed = True
48 |
49 | def initial(self):
50 | return {'%s__count' % self.alias: 0, '%s__total' % self.alias: 0}
51 |
52 | reduce_template = '{alias}__count++; {alias}__total += {lookup}'
53 | finalize_template = '{alias} = {alias}__total / {alias}__count'
54 |
55 |
56 | class Sum(MongoAggregate):
57 | is_computed = True
58 | initial_value = 0
59 |
60 | reduce_template = '{alias} += {lookup}'
61 |
62 |
63 | _AGGREGATION_CLASSES = dict((cls.__name__, cls)
64 | for cls in MongoAggregate.__subclasses__())
65 |
66 | def get_aggregation_class_by_name(name):
67 | return _AGGREGATION_CLASSES[name]
68 |
--------------------------------------------------------------------------------
/docs/source/topics/cache.rst:
--------------------------------------------------------------------------------
1 | Caching
2 | =======
3 |
4 | .. note::
5 |
6 | This document assumes that you're already familiar with
7 | `Django's caching framework`_ (database caching in particular).
8 |
9 | `Django MongoDB Cache`_ is a Django database cache backend similar to the one
10 | built into Django (which only works with SQL databases).
11 |
12 | Cache entries are structured like this:
13 |
14 | .. code-block:: js
15 |
16 | {
17 | "_id" : ,
18 | "v" : ,
19 | "e" :
20 | }
21 |
22 | Thanks to MongoDB's ``_id`` lookups being very fast, MongoDB caching may be used
23 | as a drop-in replacement for "real" cache systems such as Memcached in many cases.
24 | (Memcached is still way faster and does a better caching job in general, but the
25 | performance you get out of MongoDB should be enough for most mid-sized Web sites.)
26 |
27 | Installation
28 | ------------
29 | .. code-block:: bash
30 |
31 | git clone https://github.com/django-nonrel/mongodb-cache
32 | cd mongodb-cache
33 | python setup.py install
34 |
35 | Setup
36 | -----
37 | Please follow the instructions in the `Django db cache setup docs`_ for details
38 | on how to configure a database cache. Skip the ``createcachetable`` step since
39 | there's no need to create databases in MongoDB. Also, instead of the default db
40 | cache backend name, use ``"django_mongodb_cache.MongoDBCache"`` as ``BACKEND``::
41 |
42 | CACHES = {
43 | 'default' : {
44 | 'BACKEND' : 'django_mongodb_cache.MongoDBCache',
45 | 'LOCATION' : 'my_cache_collection'
46 | }
47 | }
48 |
49 | Django MongoDB Cache will also honor all optional settings the default database
50 | cache backend takes care of (``TIMEOUT``, ``OPTIONS``, etc).
51 |
52 | .. _Django's caching framework: https://docs.djangoproject.com/en/dev/topics/cache/
53 | .. _Django MongoDB Cache: https://github.com/django-nonrel/mongodb-cache
54 | .. _Django db cache setup docs: https://docs.djangoproject.com/en/dev/topics/cache/#database-caching
55 |
--------------------------------------------------------------------------------
/docs/source/troubleshooting.rst:
--------------------------------------------------------------------------------
1 | Troubleshooting
2 | ===============
3 |
4 | This page is going to be a collection of common issues Django MongoDB Engine
5 | users faced. Please help grow this collection --
6 | :doc:`tell us about your troubles `!
7 |
8 |
9 | .. _troubleshooting/SITE_ID:
10 |
11 | ``SITE_ID`` issues
12 | ------------------
13 | .. code-block:: none
14 |
15 | AutoField (default primary key) values must be strings representing an ObjectId on MongoDB (got u'1' instead). Please make sure your SITE_ID contains a valid ObjectId string.
16 |
17 | This means that your ``SITE_ID`` setting (`What's SITE_ID?!`_) is incorrect --
18 | it is set to "1" but the site object that has automatically been created has an
19 | ObjectId primary key.
20 |
21 | If you add ``'django_mongodb_engine'`` to your list of ``INSTALLED_APPS``, you
22 | can use the ``tellsiteid`` command to get the default site's ObjectId and update
23 | your ``SITE_ID`` setting accordingly:
24 |
25 | .. code-block:: none
26 |
27 | $ ./manage.py tellsiteid
28 | The default site's ID is u'deafbeefdeadbeef00000000'. To use the sites framework, add this line to settings.py:
29 | SITE_ID=u'deafbeefdeadbeef00000000'
30 |
31 | .. _What's SITE_ID?!: http://docs.djangoproject.com/en/dev/ref/settings/#std:setting-SITE_ID
32 |
33 |
34 | Creating/editing user in admin causes ``DatabaseError``
35 | -------------------------------------------------------
36 | .. code-block:: none
37 |
38 | DatabaseError at /admin/auth/user/deafbeefdeadbeef00000000/
39 | [...] This query is not supported by the database.
40 |
41 | This happens because Django tries to execute JOINs in order to display a list of
42 | groups/permissions in the user edit form.
43 |
44 | To workaround this problem, add ``'djangotoolbox'`` to your ``INSTALLED_APPS``
45 | which makes the Django admin skip the groups and permissions widgets.
46 |
47 | No form field implemented for
48 | ----------------------------------------------------------------------
49 | See https://gist.github.com/1200165
50 |
--------------------------------------------------------------------------------
/docs/source/meta/contributing.rst:
--------------------------------------------------------------------------------
1 | How to Contribute
2 | =================
3 |
4 | We'd love to see you getting involved in Django MongoDB Engine's development!
5 |
6 | Here are some ideas on how you can help evolve this project:
7 |
8 | * :ref:`Send us feedback `! Tell us about your good or bad
9 | experiences with Django MongoDB Engine -- what did you like and what should be improved?
10 | * Blog/write about using Django with MongoDB and
11 | :ref:`let us know about your work `.
12 | * Help solving other people's problems on :ref:`the mailing list `.
13 | * Fix and improve this documentation. Since none of the Django MongoDB Engine
14 | developers are native speakers, these documents are probably full of typos and
15 | weird, ungrammatical phrasings. Also, if you're missing something from this
16 | documentation, please tell us!
17 | * :ref:`Report bugs and feature requests `.
18 | * :ref:`Send patches or pull requests ` containing bug
19 | fixes, new features or code improvement.
20 |
21 | .. _contributing/mailinglist:
22 |
23 | Mailing List
24 | ------------
25 | Our `mailing list`_, ``django-non-relational@googlegroups.com``, is the right
26 | place for general feedback, discussion and support.
27 |
28 | Development
29 | -----------
30 | Django MongoDB Engine is being developed `on GitHub`_.
31 |
32 | .. _contributing/bugreports:
33 |
34 | Bug Reports
35 | ...........
36 | Bugs can be reported to our `ticket tracker on GitHub`_.
37 |
38 | .. _contributing/patches:
39 |
40 | Patches
41 | .......
42 | The most comfortable way to get your changes into Django MongoDB Engine is to
43 | use `GitHub's pull requests`_. It's perfectly fine, however, to send regular
44 | patches to :ref:`the mailing list `.
45 |
46 | .. _mailing list: http://groups.google.com/group/django-non-relational
47 | .. _on Github: https://github.com/django-nonrel
48 | .. _ticket tracker on GitHub: https://github.com/django-nonrel/mongodb-engine/issues/
49 | .. _GitHub's pull requests: http://help.github.com/pull-requests/
50 |
--------------------------------------------------------------------------------
/django_mongodb_engine/south.py:
--------------------------------------------------------------------------------
1 | import warnings
2 |
3 |
4 | warnings.warn(
5 | '`django_mongodb_engine.south.DatabaseOperations` south database backend '
6 | 'is actually a dummy backend that does nothing at all. It will be '
7 | 'removed in favor of the `django_mongodb_engine.south_adapter.DatabaseOperations` '
8 | 'that provides the correct behavior.',
9 | DeprecationWarning
10 | )
11 |
12 | class DatabaseOperations(object):
13 | """
14 | MongoDB implementation of database operations.
15 | """
16 |
17 | backend_name = 'django_mongodb_engine'
18 |
19 | supports_foreign_keys = False
20 | has_check_constraints = False
21 | has_ddl_transactions = False
22 |
23 | def __init__(self, db_alias):
24 | pass
25 |
26 | def add_column(self, table_name, name, field, *args, **kwds):
27 | pass
28 |
29 | def alter_column(self, table_name, name, field, explicit_name=True):
30 | pass
31 |
32 | def delete_column(self, table_name, column_name):
33 | pass
34 |
35 | def rename_column(self, table_name, old, new):
36 | pass
37 |
38 | def create_unique(self, table_name, columns):
39 | pass
40 |
41 | def delete_unique(self, table_name, columns):
42 | pass
43 |
44 | def delete_primary_key(self, table_name):
45 | pass
46 |
47 | def delete_table(self, table_name, cascade=True):
48 | pass
49 |
50 | def connection_init(self):
51 | pass
52 |
53 | def send_pending_create_signals(self, verbosity=False, interactive=False):
54 | pass
55 |
56 | def get_pending_creates(self):
57 | pass
58 |
59 | def start_transaction(self):
60 | pass
61 |
62 | def rollback_transaction(self):
63 | pass
64 |
65 | def rollback_transactions_dry_run(self):
66 | pass
67 |
68 | def clear_run_data(self, pending_creates):
69 | pass
70 |
71 | def create_table(self, unique=True, null=True, blank=True):
72 | pass
73 |
74 | def send_create_signal(self, verbosity=False, interactive=False):
75 | pass
76 |
77 | def execute_deferred_sql(self):
78 | pass
79 |
80 | def commit_transaction(self):
81 | pass
82 |
--------------------------------------------------------------------------------
/docs/source/topics/setup.rst:
--------------------------------------------------------------------------------
1 | Setup
2 | =====
3 |
4 | This page explains how to install and configure a Django/MongoDB setup.
5 |
6 | Installation
7 | ------------
8 | Django MongoDB Engine depends on
9 |
10 | * Django-nonrel_, a fork of Django that adds support for non-relational databases
11 | * djangotoolbox_, a bunch of utilities for non-relational Django applications and backends
12 |
13 | It's highly recommended (although not required) to use a virtualenv_ for your
14 | project to not mess up other Django setups.
15 |
16 | virtualenv
17 | ..........
18 | If not already installed, grab a copy from the Cheeseshop::
19 |
20 | pip install virtualenv
21 |
22 | To set up a virtual environment for your project, use ::
23 |
24 | virtualenv myproject
25 |
26 | To join the environment, use (in Bash)::
27 |
28 | source myproject/bin/activate
29 |
30 | Django-nonrel
31 | .............
32 | ::
33 |
34 | pip install git+https://github.com/django-nonrel/django@nonrel-1.5
35 |
36 | djangotoolbox
37 | .............
38 | ::
39 |
40 | pip install git+https://github.com/django-nonrel/djangotoolbox
41 |
42 | Django MongoDB Engine
43 | .....................
44 | You should use the latest Git revision. ::
45 |
46 | pip install git+https://github.com/django-nonrel/mongodb-engine
47 |
48 |
49 | Configuration
50 | -------------
51 | Database setup is easy (see also the `Django database setup docs`_)::
52 |
53 | DATABASES = {
54 | 'default' : {
55 | 'ENGINE' : 'django_mongodb_engine',
56 | 'NAME' : 'my_database'
57 | }
58 | }
59 |
60 | Django MongoDB Engine also takes into account the ``HOST``, ``PORT``, ``USER``,
61 | ``PASSWORD`` and ``OPTIONS`` settings.
62 |
63 | Possible values of ``OPTIONS`` are described in the
64 | :doc:`settings reference `.
65 |
66 | Done!
67 | -----
68 | That's it! You can now go straight ahead developing your Django application as
69 | you would do with any other database.
70 |
71 |
72 | .. _virtualenv: http://www.virtualenv.org/
73 | .. _Django database setup docs: https://docs.djangoproject.com/en/dev/ref/settings/#databases
74 | .. _djangotoolbox: https://github.com/django-nonrel/djangotoolbox
75 | .. _Django-nonrel: http://django-nonrel.org/
76 |
--------------------------------------------------------------------------------
/tests/multiple_database/models.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.contrib.auth.models import User
3 | from django.db import models
4 |
5 | class Review(models.Model):
6 | source = models.CharField(max_length=100)
7 | object_id = models.PositiveIntegerField()
8 |
9 | def __unicode__(self):
10 | return self.source
11 |
12 | class Meta:
13 | ordering = ('source',)
14 |
15 | class PersonManager(models.Manager):
16 | def get_by_natural_key(self, name):
17 | return self.get(name=name)
18 |
19 | class Person(models.Model):
20 | objects = PersonManager()
21 | name = models.CharField(max_length=100)
22 |
23 | def __unicode__(self):
24 | return self.name
25 |
26 | class Meta:
27 | ordering = ('name',)
28 |
29 | # This book manager doesn't do anything interesting; it just
30 | # exists to strip out the 'extra_arg' argument to certain
31 | # calls. This argument is used to establish that the BookManager
32 | # is actually getting used when it should be.
33 | class BookManager(models.Manager):
34 | def create(self, *args, **kwargs):
35 | kwargs.pop('extra_arg', None)
36 | return super(BookManager, self).create(*args, **kwargs)
37 |
38 | def get_or_create(self, *args, **kwargs):
39 | kwargs.pop('extra_arg', None)
40 | return super(BookManager, self).get_or_create(*args, **kwargs)
41 |
42 | class Book(models.Model):
43 | objects = BookManager()
44 | title = models.CharField(max_length=100)
45 | published = models.DateField()
46 | editor = models.ForeignKey(Person, null=True, related_name='edited')
47 | pages = models.IntegerField(default=100)
48 |
49 | def __unicode__(self):
50 | return self.title
51 |
52 | class Meta:
53 | ordering = ('title',)
54 |
55 | class Pet(models.Model):
56 | name = models.CharField(max_length=100)
57 | owner = models.ForeignKey(Person)
58 |
59 | def __unicode__(self):
60 | return self.name
61 |
62 | class Meta:
63 | ordering = ('name',)
64 |
65 | class UserProfile(models.Model):
66 | user = models.OneToOneField(User, null=True)
67 | flavor = models.CharField(max_length=100)
68 |
69 | class Meta:
70 | ordering = ('flavor',)
71 |
--------------------------------------------------------------------------------
/docs/source/reference/settings.rst:
--------------------------------------------------------------------------------
1 | Settings
2 | ========
3 |
4 | .. TODO fix highlighting
5 |
6 | Client Settings
7 | -------------------
8 | Additional flags may be passed to :class:`pymongo.MongoClient` using the
9 | ``OPTIONS`` dictionary::
10 |
11 | DATABASES = {
12 | 'default' : {
13 | 'ENGINE' : 'django_mongodb_engine',
14 | 'NAME' : 'my_database',
15 | ...
16 | 'OPTIONS' : {
17 | 'socketTimeoutMS' : 500,
18 | ...
19 | }
20 | }
21 | }
22 |
23 | All of these settings directly mirror PyMongo settings. In fact, all Django
24 | MongoDB Engine does is lower-casing the names before passing the flags to
25 | :class:`~pymongo.MongoClient`. For a list of possible options head over to the
26 | `PyMongo documentation on client options`_.
27 |
28 | .. _operations-setting:
29 |
30 | Acknowledged Operations
31 | -----------------------
32 | Use the ``OPERATIONS`` dict to specify extra flags passed to
33 | :meth:`Collection.save `,
34 | :meth:`~pymongo.collection.Collection.update` or
35 | :meth:`~pymongo.collection.Collection.remove` (and thus included in the write concern):
36 |
37 | .. code-block:: python
38 |
39 | 'OPTIONS' : {
40 | 'OPERATIONS' : {'w' : 3},
41 | ...
42 | }
43 |
44 |
45 |
46 | Get a more fine-grained setup by introducing another layer to this dict:
47 |
48 | .. code-block:: python
49 |
50 | 'OPTIONS' : {
51 | 'OPERATIONS' : {
52 | 'save' : {'w' : 3},
53 | 'update' : {},
54 | 'delete' : {'j' : True}
55 | },
56 | ...
57 | }
58 |
59 | .. note::
60 |
61 | This operations map to the **Django** operations `save`, `update` and `delete`
62 | (**not** to MongoDB operations). This is because Django abstracts
63 | "`insert vs. update`" into `save`.
64 |
65 |
66 | A full list of write concern flags may be found in the
67 | `MongoDB documentation `_.
68 |
69 | .. _Similar to Django's built-in backends:
70 | http://docs.djangoproject.com/en/dev/ref/settings/#std:setting-OPTIONS
71 | .. _PyMongo documentation on client options:
72 | http://api.mongodb.org/python/current/api/pymongo/mongo_client.html
73 |
--------------------------------------------------------------------------------
/docs/source/reference/model-options.rst:
--------------------------------------------------------------------------------
1 | Model Options
2 | =============
3 |
4 | In addition to Django's `default Meta options`_, Django MongoDB Engine supports
5 | various options specific to MongoDB through a special ``class MongoMeta``. ::
6 |
7 | class FooModel(models.Model):
8 | ...
9 | class MongoMeta:
10 | # Mongo options here
11 | ...
12 |
13 | Indexes
14 | -------
15 | Django MongoDB Engine already understands the standard
16 | :attr:`~django.db.models.Field.db_index` and
17 | :attr:`~django.db.models.Options.unique_together` options and generates the
18 | corresponding MongoDB indexes on ``syncdb``.
19 |
20 | To make use of other index features, like multi-key indexes and Geospatial
21 | Indexing, additional indexes can be specified using the ``indexes`` setting. ::
22 |
23 | class Club(models.Model):
24 | location = ListField()
25 | rating = models.FloatField()
26 | admission = models.IntegerField()
27 | ...
28 | class MongoMeta:
29 | indexes = [
30 | [('rating', -1)],
31 | [('rating', -1), ('admission', 1)],
32 | {'fields': [('location', '2d')], 'min': -42, 'max': 42},
33 | ]
34 |
35 | ``indexes`` can be specified in two ways:
36 |
37 | * The simple "without options" form is a list of ``(field, direction)`` pairs.
38 | For example, a single ascending index (the same thing you get using ``db_index``)
39 | is expressed as ``[(field, 1)]``. A multi-key, descending index can be written
40 | as ``[(field1, -1), (field2, -1), ...]``.
41 | * The second form is slightly more verbose but takes additional MongoDB index
42 | options. A descending, sparse index for instance may be expressed as
43 | ``{'fields': [(field, -1)], 'sparse': True}``.
44 |
45 |
46 | Capped Collections
47 | ------------------
48 | Use the ``capped`` option and ``collection_size`` (and/or ``collection_max``)
49 | to limit a collection in size (and/or document count), new documents replacing
50 | old ones after reaching one of the limit sets.
51 |
52 | For example, a logging collection fixed to 50MiB could be defined as follows::
53 |
54 | class LogEntry(models.Model):
55 | timestamp = models.DateTimeField()
56 | message = models.TextField()
57 | ...
58 | class MongoMeta:
59 | capped = True
60 | collection_size = 50*1024*1024
61 |
62 | .. _default Meta options: http://docs.djangoproject.com/en/dev/topics/db/models/#meta-options
63 | .. _sparse: http://www.mongodb.org/display/DOCS/Indexes#Indexes-SparseIndexes
64 |
--------------------------------------------------------------------------------
/django_mongodb_engine/router.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.core.exceptions import ImproperlyConfigured
3 |
4 |
5 | _mongodbs = []
6 |
7 | def _init_mongodbs():
8 | for name, options in settings.DATABASES.iteritems():
9 | if options['ENGINE'] != 'django_mongodb_engine':
10 | continue
11 | if options.get('IS_DEFAULT'):
12 | _mongodbs.insert(0, name)
13 | else:
14 | _mongodbs.append(name)
15 |
16 | if not _mongodbs:
17 | raise ImproperlyConfigured("No MongoDB database found in "
18 | "settings.DATABASES.")
19 |
20 |
21 | class MongoDBRouter(object):
22 | """
23 | A Django router to manage models that should be stored in MongoDB.
24 |
25 | MongoDBRouter uses the MONGODB_MANAGED_APPS and MONGODB_MANAGED_MODELS
26 | settings to know which models/apps should be stored inside MongoDB.
27 |
28 | See: http://docs.djangoproject.com/en/dev/topics/db/multi-db/#topics-db-multi-db-routing
29 | """
30 |
31 | def __init__(self):
32 | if not _mongodbs:
33 | _init_mongodbs()
34 | self.managed_apps = [app.split('.')[-1] for app in
35 | getattr(settings, 'MONGODB_MANAGED_APPS', [])]
36 | self.managed_models = getattr(settings, 'MONGODB_MANAGED_MODELS', [])
37 |
38 | def is_managed(self, model):
39 | """
40 | Returns True if the model passed is managed by Django MongoDB
41 | Engine.
42 | """
43 | if model._meta.app_label in self.managed_apps:
44 | return True
45 | full_name = '%s.%s' % (model._meta.app_label, model._meta.object_name)
46 | return full_name in self.managed_models
47 |
48 | def db_for_read(self, model, **hints):
49 | """
50 | Points all operations on MongoDB models to a MongoDB database.
51 | """
52 | if self.is_managed(model):
53 | return _mongodbs[0]
54 |
55 | db_for_write = db_for_read # Same algorithm.
56 |
57 | def allow_relation(self, obj1, obj2, **hints):
58 | """
59 | Allows any relation if a model in myapp is involved.
60 | """
61 | return self.is_managed(obj2) or None
62 |
63 | def allow_syncdb(self, db, model):
64 | """
65 | Makes sure that MongoDB models only appear on MongoDB databases.
66 | """
67 | if db in _mongodbs:
68 | return self.is_managed(model)
69 | elif self.is_managed(model):
70 | return db in _mongodbs
71 | return None
72 |
--------------------------------------------------------------------------------
/docs/source/topics/gridfs.rst:
--------------------------------------------------------------------------------
1 | GridFS
2 | ======
3 |
4 | MongoDB's built-in distributed file system, GridFS_, can be used in Django
5 | applications in two different ways.
6 |
7 | In most cases, you should use the GridFS :ref:`storage backend `
8 | provided by Django MongoDB Engine.
9 |
10 | .. _gridfs/storage:
11 |
12 | Storage
13 | -------
14 | :class:`~django_mongodb_engine.storage.GridFSStorage` is a `Django storage`_
15 | that stores files in GridFS. That means it can be used with whatever component
16 | makes use of storages -- most importantly,
17 | :class:`~django.db.models.fields.files.FileField`.
18 |
19 | It uses a special collection for storing files, by default named "storage". ::
20 |
21 | from django_mongodb_engine.storage import GridFSStorage
22 |
23 | gridfs = GridFSStorage()
24 | uploads = GridFSStorage(location='/uploads')
25 |
26 | .. warning::
27 |
28 | To serve files out of GridFS, use tools like
29 | `nginx-gridfs `_.
30 | **Never** serve files through Django in production!
31 |
32 |
33 | Model Field
34 | -----------
35 | (You should probably be using the :ref:`GridFS storage backend `.)
36 |
37 | Use :class:`~django_mongodb_engine.fields.GridFSField` to store "nameless" blobs
38 | besides documents that would normally go into the document itself.
39 |
40 | All that's kept in the document is a reference (an ObjectId) to the GridFS blobs
41 | which are retrieved on demand.
42 |
43 | Assuming you want to store a 10MiB blob "in" each document, this is what you
44 | *shouldn't* do::
45 |
46 | # DON'T DO THIS
47 | class Bad(models.Model):
48 | blob = models.TextField()
49 |
50 | # NEITHER THIS
51 | class EventWorse(models.Model):
52 | blob = models.CharField(max_length=10*1024*1024)
53 |
54 | Instead, use :class:`~django_mongodb_engine.fields.GridFSField`::
55 |
56 | class Better(models.Model):
57 | blob = GridFSField()
58 |
59 | A GridFSField may be fed with anything that PyMongo can handle, that is,
60 | (preferably) file-like objects and strings.
61 |
62 | You'll always get a :class:`~gridfs.grid_file.GridOut` for documents from the
63 | database. ::
64 |
65 | >>> doc = Better()
66 |
67 | GridFSField takes file-likes (and strings)...
68 | >>> doc.blob = file_like
69 | >>> doc.save()
70 |
71 | ... and always returns GridOuts.
72 | >>> samedoc = Better.objects.get(...)
73 | >>> samedoc.blob
74 |
75 |
76 | .. _GridFS: http://docs.mongodb.org/manual/core/gridfs/
77 | .. _Django storage: https://docs.djangoproject.com/en/dev/topics/files/#file-storage
78 |
--------------------------------------------------------------------------------
/tests/runtests.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 | from types import ModuleType
5 |
6 |
7 | def runtests(foo, settings='settings', extra=[], test_builtin=False):
8 | if isinstance(foo, ModuleType):
9 | settings = foo.__name__
10 | apps = foo.INSTALLED_APPS
11 | else:
12 | apps = foo
13 | if not test_builtin:
14 | apps = filter(lambda name: not name.startswith('django.contrib.'),
15 | apps)
16 | # pre-1.6 test runners don't understand full module names
17 | import django
18 | if django.VERSION < (1, 6):
19 | apps = [app.replace('django.contrib.', '') for app in apps]
20 | execute(['./manage.py', 'test', '--settings', settings] + extra + apps)
21 |
22 |
23 | def execute_python(lines):
24 | from textwrap import dedent
25 | return execute(
26 | [sys.executable, '-c', dedent(lines)],
27 | env=dict(os.environ, DJANGO_SETTINGS_MODULE='settings',
28 | PYTHONPATH='..'))
29 |
30 |
31 | def main(short):
32 | # Run some basic tests outside Django's test environment.
33 | execute_python('''
34 | from mongodb.models import RawModel
35 | RawModel.objects.create(raw=41)
36 | RawModel.objects.update(raw=42)
37 | RawModel.objects.all().delete()
38 | RawModel.objects.create(raw=42)
39 | ''')
40 |
41 | import settings
42 | import settings.dbindexer
43 | import settings.slow_tests
44 |
45 | runtests(settings, extra=['--failfast'] if short else [])
46 |
47 | # Assert we didn't touch the production database.
48 | execute_python('''
49 | from mongodb.models import RawModel
50 | assert RawModel.objects.get().raw == 42
51 | ''')
52 |
53 | if short:
54 | exit()
55 |
56 | # Make sure we can syncdb.
57 | execute(['./manage.py', 'syncdb', '--noinput'])
58 |
59 | runtests(settings.dbindexer)
60 | runtests(['router'], 'settings.router')
61 | runtests(settings.INSTALLED_APPS, 'settings.debug')
62 | runtests(settings.slow_tests, test_builtin=True)
63 |
64 |
65 | if __name__ == '__main__':
66 | import sys
67 | if 'ignorefailures' in sys.argv:
68 | from subprocess import call as execute
69 | else:
70 | from subprocess import check_call as execute
71 | if 'coverage' in sys.argv:
72 |
73 | def _new_check_call_closure(old_check_call):
74 |
75 | def _new_check_call(cmd, **kwargs):
76 | if not cmd[0].endswith('python'):
77 | cmd = ['coverage', 'run', '-a', '--source',
78 | '../django_mongodb_engine'] + cmd
79 | return old_check_call(cmd, **kwargs)
80 |
81 | return _new_check_call
82 |
83 | execute = _new_check_call_closure(execute)
84 | main('short' in sys.argv)
85 |
--------------------------------------------------------------------------------
/django_mongodb_engine/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | __version__ = (0, 6, 0)
5 | __author__ = "Flavio Percoco Premoli, Alberto Paro, " + \
6 | "Jonas Haag and contributors"
7 | __contact__ = "django-non-relational@googlegroups.com"
8 | __homepage__ = "http://django-nonrel.org/"
9 | __docformat__ = "restructuredtext"
10 |
11 | try:
12 | from django.conf import settings
13 | from django.core.exceptions import ImproperlyConfigured
14 | except ImportError as exc:
15 | # setup.py imports this file in order to read version/author/... metadata
16 | # but does not necessarily have a Django context.
17 | import logging
18 | logging.error('Error while trying to get django'
19 | ' settings module.\nError was: {0}'.format(str(exc)))
20 | else:
21 | try:
22 | # It might be irritating that django-mongodb-engine registers itself as
23 | # an app, and I think this is worth an explanation - so here you go:
24 | # django-mongodb-engine provides a way to set MongoDB-specific options
25 | # for a certain model via the 'MongoMeta' class/attribute (similar to
26 | # the Django-style 'Meta' class). We want those options to be copied
27 | # into the model's '_meta' object, right after the class has been
28 | # defined. For this, we have to listen to the 'class_prepared' signal
29 | # from 'django.db.models.signals'. Because the 'django_mongodb_engine'
30 | # module gets imported as part of the initialization process of
31 | # Django's ORM ('django.db'), we can *not* import anything from
32 | # 'django.db' in this file (or any other submodule that is imported
33 | # during the ORM initialization) because that would get us into
34 | # recursive import hell which the Python interpreter doesn't allow. The
35 | # only way to make sure certain code is executed after Django's ORM has
36 | # been initialized is registering an app. After initializing itself,
37 | # Django imports all apps defined in the project's 'settings.py' in the
38 | # order implied by iterating over the INSTALLED_APPS list. As we have
39 | # to make sure that django-mongodb-engine is loaded very first, we
40 | # prepend it to the list and gracefully handle when it's a tuple.
41 | if isinstance(settings.INSTALLED_APPS, tuple):
42 | settings.INSTALLED_APPS = ('django_mongodb_engine',) + settings.INSTALLED_APPS
43 | else:
44 | settings.INSTALLED_APPS.insert(0, 'django_mongodb_engine')
45 | except (ImportError, ImproperlyConfigured) as exc:
46 | # setup.py imports this file in order to read version/author/... metadata
47 | # but does not necessarily have a Django context.
48 | import logging
49 | logging.error('Error while trying to get django'
50 | ' settings module.\nError was: {0}'.format(str(exc)))
51 |
--------------------------------------------------------------------------------
/docs/source/topics/lists-and-dicts.rst:
--------------------------------------------------------------------------------
1 | Lists & Dicts
2 | =============
3 |
4 | Django MongoDB Engine provides two fields for storing arbitrary (BSON-compatible)
5 | Python :class:`list` and :class:`dict` objects in Django model objects,
6 | :ref:`ListField ` and :ref:`DictField `,
7 | which can be used to store information that is not worth a separate model or
8 | that should be queryable in efficient manner (using an index).
9 |
10 | Both fields may optionally be provided with type information. That restricts
11 | their usage to one single type but has the advantage of automatic type checks
12 | and conversions.
13 |
14 | .. _topics/listfield:
15 |
16 | ListField
17 | ---------
18 | Stores Python lists (or any other iterable), represented in BSON as arrays. ::
19 |
20 | from djangotoolbox.fields import ListField
21 |
22 | class Post(models.Model):
23 | ...
24 | tags = ListField()
25 |
26 | ::
27 |
28 | >>> Post(tags=['django', 'mongodb'], ...).save()
29 | >>> Post.objects.get(...).tags
30 | ['django', 'mongodb']
31 |
32 | The typed variant automatically does type conversions according to the given type::
33 |
34 | class Post(models.Model):
35 | ...
36 | edited_on = ListField(models.DateTimeField())
37 |
38 | ::
39 |
40 | >>> post = Post(edited_on=['1010-10-10 10:10:10'])
41 | >>> post.save()
42 | >>> Post.objects.get(...).edited_on
43 | [datetime.datetime([1010, 10, 10, 10, 10, 10])]
44 |
45 | As described :ref:`in the tutorial `, ListFields are
46 | very useful when used together with :doc:`embedded-models` to store lists of
47 | sub-entities to model 1-to-n relationships::
48 |
49 | from djangotoolbox.fields import EmbeddedModelField, ListField
50 |
51 | class Post(models.Model):
52 | ...
53 | comments = ListField(EmbeddedModelField('Comment'))
54 |
55 | class Comment(models.Model):
56 | ...
57 | text = models.TextField()
58 |
59 | Please head over to the :doc:`embedded-models` topic for more about embedded models.
60 |
61 | SetField
62 | --------
63 | Much like a :ref:`ListField ` except that it's represented as
64 | a :class:`set` on Python side (but stored as a list on MongoDB due to the lack
65 | of a separate set type in BSON).
66 |
67 | .. _topics/dictfield:
68 |
69 | DictField
70 | ---------
71 | Stores Python dicts (or any dict-like iterable), represented in BSON as subobjects. ::
72 |
73 | from djangotoolbox.fields import DictField
74 |
75 | class Image(models.Model):
76 | ...
77 | exif = DictField()
78 |
79 | ::
80 |
81 | >>> Image(exif=get_exif_data(...), ...).save()
82 | >>> Image.objects.get(...).exif
83 | {u'camera_model' : 'Spamcams 4242', 'exposure_time' : 0.3, ...}
84 |
85 | The typed variant automatically does type conversion on values. (Not on keys
86 | as the are required to be strings on MongoDB.) ::
87 |
88 | class Poll(models.Model):
89 | ...
90 | votes = DictField(models.IntegerField())
91 |
92 | ::
93 |
94 | >>> Poll(votes={'bob' : 3.14, 'alice' : '42'}, ...).save()
95 | >>> Poll.objects.get(...).votes
96 | {u'bob' : 3, u'alice' : 42}
97 |
98 | DictFields are useful mainly for storing objects of varying shape, i.e. objects
99 | whose structure is unknow at coding time. If all your objects have the same
100 | structure, you should consider using :doc:`embedded-models`.
101 |
--------------------------------------------------------------------------------
/CHANGELOG.rst:
--------------------------------------------------------------------------------
1 | Changelog
2 | =========
3 |
4 | .. currentmodule:: djangotoolbox.fields
5 |
6 | Version 0.6 (Jul 12, 2015)
7 | -----------------
8 | * Add support for PyMongo 3 (Thanks @markunsworth & @ajdavis)
9 |
10 |
11 | Version 0.5.2 (Jun 19, 2015)
12 | -----------------
13 | * Add support for Replica Sets (Thanks @r4fek)
14 | * Make safe writes the default (Thanks @markunsworth)
15 |
16 |
17 | Version 0.5.1 (Nov 2013)
18 | -----------------
19 | * Fixed packaging issues
20 |
21 |
22 | Version 0.5 (Nov 2013)
23 | -----------------
24 | Major changes
25 | ~~~~~~~~~~~~~
26 | * Added support for Django 1.4-1.6, requires djangotoolbox >= 1.6.0
27 | * PyPy support
28 | * MongoDB 2.0 support
29 | * We're now on Travis_
30 | * New custom primary key behavior (to be documented)
31 | * New ``MongoMeta.indexes`` system (see :doc:`/reference/model-options`),
32 | deprecation of ``MongoMeta.{index_together,descending_indexes,sparse_indexes}``
33 |
34 | Minor changes/fixes
35 | ~~~~~~~~~~~~~~~~~~~
36 | * Support for MongoDB :meth:`~django_mongodb_engine.contrib.MongoDBManager.distinct` queries
37 | * Support for reversed-``$natural`` ordering using :meth:`~django.db.query.QuerySet.reverse`
38 | * Dropped ``LegacyEmbeddedModelField``
39 | * ``url()`` support for the :doc:`GridFS Storage `
40 | * Deprecation of ``A()`` queries
41 | * Deprecation of the ``GridFSField.versioning`` feature
42 | * Numerous query generator fixes
43 | * Fixed ``DecimalField`` values sorting
44 | * Other bug fixes, cleanup, new tests etc.
45 |
46 |
47 | Version 0.4 (May 2011)
48 | ----------------------
49 | * :doc:`GridFS storage backend `
50 | * Fulltext search
51 | * Query logging support
52 | * Support for sparse indexes (see :doc:`/reference/model-options`)
53 | * Database settings specific to MongoDB were moved into the ``OPTIONS`` dict.
54 | (see :doc:`/reference/settings`)
55 | Furthermore, the `SAFE_INSERTS` and `WAIT_FOR_SLAVES` flags are now deprecated
56 | in favor of the new ``OPERATIONS`` setting (see :ref:`operations-setting`)
57 | * Added the :ref:`tellsiteid command `
58 | * Defined a stable :ref:`lower-level database API `
59 | * Numerous bug fixes, new tests, code improvements and deprecations
60 |
61 |
62 | Version 0.3 (Jan 2011)
63 | ----------------------
64 | * *OR* query support
65 | * Support for :class:`~django.db.models.DateTimeField` and friends
66 | * Support for atomic updates using F_
67 | * :class:`EmbeddedModelField` has been `merged into djangotoolbox`_.
68 | For legacy data records in your setup, you can use the ``LegacyEmbeddedModelField``.
69 | * Support for :ref:`raw queries and raw updates `
70 |
71 | .. * Added a flag to enable :ref:`model-referencing`
72 |
73 |
74 | Version 0.2 (Oct 2010)
75 | ----------------------
76 | * :doc:`Aggregation support `
77 | * :doc:`Map/Reduce support `
78 | * :class:`ListField`, :class:`SetListField`, :class:`DictField` and
79 | :class:`GenericField` have been `merged into djangotoolbox`_
80 | * Added an :class:`EmbeddedModelField` to store arbitrary model instances as
81 | MongoDB embedded objects/subobjects.
82 | * Internal Refactorings
83 |
84 | .. * Support for queries and updates on embedded models (see :ref:`embedded-object-queries`)
85 |
86 | .. _merged into djangotoolbox: https://github.com/django-nonrel/djangotoolbox/blob/master/djangotoolbox/fields.py
87 | .. _F: http://docs.djangoproject.com/en/dev/topics/db/queries/#query-expressions
88 | .. _Travis: http://travis-ci.org/django-nonrel/mongodb-engine
89 |
--------------------------------------------------------------------------------
/django_mongodb_engine/utils.py:
--------------------------------------------------------------------------------
1 | import re
2 | import time
3 |
4 | from django.conf import settings
5 | from django.db.backends.util import logger
6 |
7 | from pymongo import ASCENDING
8 | from pymongo.cursor import Cursor
9 |
10 |
11 | def first(test_func, iterable):
12 | for item in iterable:
13 | if test_func(item):
14 | return item
15 |
16 |
17 | def safe_regex(regex, *re_args, **re_kwargs):
18 |
19 | def wrapper(value):
20 | return re.compile(regex % re.escape(value), *re_args, **re_kwargs)
21 | wrapper.__name__ = 'safe_regex (%r)' % regex
22 |
23 | return wrapper
24 |
25 |
26 | def make_struct(*attrs):
27 |
28 | class _Struct(object):
29 | __slots__ = attrs
30 |
31 | def __init__(self, *args):
32 | for attr, arg in zip(self.__slots__, args):
33 | setattr(self, attr, arg)
34 |
35 | return _Struct
36 |
37 |
38 | def make_index_list(indexes):
39 | if isinstance(indexes, basestring):
40 | indexes = [indexes]
41 | for index in indexes:
42 | if not isinstance(index, tuple):
43 | index = index, ASCENDING
44 | yield index
45 |
46 |
47 | class CollectionDebugWrapper(object):
48 |
49 | def __init__(self, collection, db_alias):
50 | self.collection = collection
51 | self.alias = db_alias
52 |
53 | def __getattr__(self, attr):
54 | return getattr(self.collection, attr)
55 |
56 | def profile_call(self, func, args=(), kwargs={}):
57 | start = time.time()
58 | retval = func(*args, **kwargs)
59 | duration = time.time() - start
60 | return duration, retval
61 |
62 | def log(self, op, duration, args, kwargs=None):
63 | args = ' '.join(str(arg) for arg in args)
64 | msg = '%s.%s (%.2f) %s' % (self.collection.name, op, duration, args)
65 | kwargs = dict((k, v) for k, v in kwargs.iteritems() if v)
66 | if kwargs:
67 | msg += ' %s' % kwargs
68 | if len(settings.DATABASES) > 1:
69 | msg = self.alias + '.' + msg
70 | logger.debug(msg, extra={'duration': duration})
71 |
72 | def find(self, *args, **kwargs):
73 | return DebugCursor(self, self.collection, *args, **kwargs)
74 |
75 | def logging_wrapper(method):
76 |
77 | def wrapper(self, *args, **kwargs):
78 | func = getattr(self.collection, method)
79 | duration, retval = self.profile_call(func, args, kwargs)
80 | self.log(method, duration, args, kwargs)
81 | return retval
82 |
83 | return wrapper
84 |
85 | save = logging_wrapper('save')
86 | remove = logging_wrapper('remove')
87 | update = logging_wrapper('update')
88 | map_reduce = logging_wrapper('map_reduce')
89 | inline_map_reduce = logging_wrapper('inline_map_reduce')
90 |
91 | del logging_wrapper
92 |
93 |
94 | class DebugCursor(Cursor):
95 |
96 | def __init__(self, collection_wrapper, *args, **kwargs):
97 | self.collection_wrapper = collection_wrapper
98 | super(DebugCursor, self).__init__(*args, **kwargs)
99 |
100 | def _refresh(self):
101 | super_meth = super(DebugCursor, self)._refresh
102 | if self._Cursor__id is not None:
103 | return super_meth()
104 | # self.__id is None: first time the .find() iterator is
105 | # entered. find() profiling happens here.
106 | duration, retval = self.collection_wrapper.profile_call(super_meth)
107 | kwargs = {'limit': self._Cursor__limit, 'skip': self._Cursor__skip,
108 | 'sort': self._Cursor__ordering}
109 | self.collection_wrapper.log('find', duration, [self._Cursor__spec],
110 | kwargs)
111 | return retval
112 |
--------------------------------------------------------------------------------
/docs/source/topics/embedded-models.rst:
--------------------------------------------------------------------------------
1 | Embedded Models
2 | ===============
3 |
4 | Django MongoDB Engine supports `MongoDB's subobjects`_ which can be used
5 | to embed an object into another.
6 |
7 | Using :ref:`topics/listfield` and :ref:`topics/dictfield` it's already possible
8 | to embed objects (:class:`dicts `) of arbitrary shape.
9 |
10 | However, EmbeddedModelField (described beneath) is a much more comfortable tool
11 | for many use cases, ensuring the data you store actually matches the structure
12 | and types you want it to be in.
13 |
14 | The Basics
15 | ----------
16 | Let's consider this example::
17 |
18 | from djangotoolbox.fields import EmbeddedModelField
19 |
20 | class Customer(models.Model):
21 | name = models.CharField(...)
22 | address = EmbeddedModelField('Address')
23 | ...
24 |
25 | class Address(models.Model):
26 | ...
27 | city = models.CharField(...)
28 |
29 | The API feels very natural and is similar to that of Django's relation fields. ::
30 |
31 | >>> Customer(name='Bob', address=Address(city='New York', ...), ...).save()
32 | >>> bob = Customer.objects.get(...)
33 | >>> bob.address
34 |
35 | >>> bob.address.city
36 | 'New York'
37 |
38 | Represented in BSON, Bob's structure looks like this:
39 |
40 | .. code-block:: js
41 |
42 | {
43 | "_id": ObjectId(...),
44 | "name": "Bob",
45 | "address": {
46 | ...
47 | "city": "New York"
48 | },
49 | ...
50 | }
51 |
52 | While such "flat" embedding is useful if you want to bundle multiple related
53 | fields into one common namespace -- for instance, in the example above we
54 | bundled all information about a customers' address into the `address` namespace
55 | -- there's a much more common usecase for embedded objects: one-to-many relations.
56 |
57 | .. _topics/list-of-subobjects:
58 |
59 | Lists of Subobjects (One-to-Many Relations)
60 | -------------------------------------------
61 | Often, lists of subobjects are superior to relations (in terms of simplicity and
62 | performance) for modeling one-to-many relationships between models.
63 |
64 | Consider this elegant way to implement the Post ⇔ Comments relationship::
65 |
66 | from djangotoolbox.fields import ListField, EmbeddedModelField
67 |
68 | class Post(models.Model):
69 | ...
70 | comments = ListField(EmbeddedModelField('Comment'))
71 |
72 | class Comment(models.Model):
73 | text = models.TextField()
74 |
75 | Embedded objects are represented as subobjects on MongoDB::
76 |
77 | >>> comments = [Comment(text='foo'), Comment(text='bar')]
78 | >>> Post(comments=comments, ...).save()
79 | >>> Post.objects.get(...).comments
80 | [, ]
81 |
82 | .. code-block:: js
83 |
84 | {
85 | "_id": ObjectId(...),
86 | ...
87 | "comments" : [
88 | {"text": "foo", },
89 | {"text": "bar"}
90 | ]
91 | }
92 |
93 | Generic Embedding
94 | -----------------
95 | Similar to Django's `generic relations`_, it's possible to embed objects of any
96 | type (sometimes referred to as "polymorphic" relationships). This works by
97 | adding the model's name and module to each subobject, accompanying the actual
98 | data with type information:
99 |
100 | .. code-block:: js
101 |
102 | {
103 | "_id" : ObjectId(...),
104 | "stuff" : [
105 | {"foo" : 42, "_module" : "demoapp.models", "_model" : "FooModel"},
106 | {"bar" : "spam", "_module" : "demoapp.models", "_model" : "FooModel"}
107 | ]
108 | }
109 |
110 | As you can see, generic embedded models add a lot of overhead that bloats up
111 | your data records. If you want to use them anyway, here's how you'd do it::
112 |
113 | class Container(models.Model):
114 | stuff = ListField(EmbeddedModelField())
115 |
116 | class FooModel(models.Model):
117 | foo = models.IntegerField()
118 |
119 | class BarModel(models.Model):
120 | bar = models.CharField(max_length=255)
121 |
122 | ::
123 |
124 | Container.objects.create(
125 | stuff=[FooModel(foo=42), BarModel(bar='spam')]
126 | )
127 |
128 | .. _MongoDB's subobjects: http://docs.mongodb.org/manual/core/data-model-design/
129 | .. _generic relations: https://docs.djangoproject.com/en/dev/ref/contrib/contenttypes/
130 |
--------------------------------------------------------------------------------
/django_mongodb_engine/south_adapter.py:
--------------------------------------------------------------------------------
1 | # This is needed until the sibling south module is removed
2 | from __future__ import absolute_import
3 |
4 | from django.core.exceptions import ImproperlyConfigured
5 | from django.db.models.fields import NOT_PROVIDED
6 | from django.db.utils import IntegrityError
7 | from pymongo.errors import DuplicateKeyError
8 |
9 | from .utils import make_index_list
10 |
11 |
12 | try:
13 | from south.db.generic import DatabaseOperations
14 | except ImportError:
15 | raise ImproperlyConfigured('Make sure to install south before trying to '
16 | 'import this module.')
17 |
18 |
19 | class DatabaseOperations(DatabaseOperations):
20 | """
21 | MongoDB implementation of database operations.
22 | """
23 |
24 | backend_name = 'django_mongodb_engine'
25 |
26 | supports_foreign_keys = False
27 | has_check_constraints = False
28 | has_ddl_transactions = False
29 |
30 | def _get_collection(self, name):
31 | return self._get_connection().get_collection(name)
32 |
33 | def add_column(self, table_name, field_name, field, keep_default=True):
34 | # Make sure the field is correctly prepared
35 | field.set_attributes_from_name(field_name)
36 | if field.has_default():
37 | default = field.get_default()
38 | if default is not None:
39 | connection = self._get_connection()
40 | collection = self._get_collection(table_name)
41 | name = field.column
42 | db_prep_save = field.get_db_prep_save(default, connection=connection)
43 | default = connection.ops.value_for_db(db_prep_save, field)
44 | # Update all the documents that haven't got this field yet
45 | collection.update({name: {'$exists': False}},
46 | {'$set': {name: default}})
47 | if not keep_default:
48 | field.default = NOT_PROVIDED
49 |
50 | def alter_column(self, table_name, column_name, field, explicit_name=True):
51 | # Since MongoDB is schemaless there's no way to coerce field datatype
52 | pass
53 |
54 | def delete_column(self, table_name, name):
55 | collection = self._get_collection(table_name)
56 | collection.update({}, {'$unset': {name: 1}})
57 |
58 | def rename_column(self, table_name, old, new):
59 | collection = self._get_collection(table_name)
60 | collection.update({}, {'$rename': {old: new}})
61 |
62 | def create_unique(self, table_name, columns, drop_dups=False):
63 | collection = self._get_collection(table_name)
64 | try:
65 | index_list = list(make_index_list(columns))
66 | collection.create_index(index_list, unique=True, drop_dups=drop_dups)
67 | except DuplicateKeyError as e:
68 | raise IntegrityError(e)
69 |
70 | def delete_unique(self, table_name, columns):
71 | collection = self._get_collection(table_name)
72 | index_list = list(make_index_list(columns))
73 | collection.drop_index(index_list)
74 |
75 | def delete_primary_key(self, table_name):
76 | # MongoDB doesn't support primary key deletion
77 | pass
78 |
79 | def create_table(self, table_name, fields, **kwargs):
80 | # Collection creation is automatic but code calling this might expect
81 | # it to exist, thus we create it here. i.e. Calls to `rename_table` will
82 | # fail if the collection doesn't already exist.
83 | connection = self._get_connection()
84 | connection.database.create_collection(table_name, **kwargs)
85 |
86 | def rename_table(self, table_name, new_table_name):
87 | collection = self._get_collection(table_name)
88 | collection.rename(new_table_name)
89 |
90 | def delete_table(self, table_name, cascade=True):
91 | connection = self._get_connection()
92 | connection.database.drop_collection(table_name)
93 |
94 | def start_transaction(self):
95 | # MongoDB doesn't support transactions
96 | pass
97 |
98 | def rollback_transaction(self):
99 | # MongoDB doesn't support transactions
100 | pass
101 |
102 | def commit_transaction(self):
103 | # MongoDB doesn't support transactions
104 | pass
105 |
106 | def rollback_transactions_dry_run(self):
107 | # MongoDB doesn't support transactions
108 | pass
109 |
--------------------------------------------------------------------------------
/tests/mongodb/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | from djangotoolbox.fields import RawField, ListField, DictField, \
4 | EmbeddedModelField
5 |
6 | from django_mongodb_engine.fields import GridFSField, GridFSString
7 |
8 | # ensures class_prepared signal handler is installed
9 | from django_mongodb_engine import models as mongo_models
10 |
11 | from query.models import Post
12 |
13 |
14 | class DescendingIndexModel(models.Model):
15 | desc = models.IntegerField()
16 |
17 | class MongoMeta:
18 | descending_indexes = ['desc']
19 |
20 |
21 | class DateModel(models.Model):
22 | date = models.DateField()
23 |
24 |
25 | class DateTimeModel(models.Model):
26 | datetime = models.DateTimeField()
27 |
28 |
29 | class RawModel(models.Model):
30 | raw = RawField()
31 |
32 |
33 | class IndexTestModel(models.Model):
34 | regular_index = models.IntegerField(db_index=True)
35 | custom_column = models.IntegerField(db_column='foo', db_index=True)
36 | descending_index = models.IntegerField(db_index=True)
37 | descending_index_custom_column = models.IntegerField(db_column='bar',
38 | db_index=True)
39 | foreignkey_index = models.ForeignKey(RawModel, db_index=True, on_delete=models.DO_NOTHING)
40 | foreignkey_custom_column = models.ForeignKey('DateModel',
41 | db_column='spam')
42 | sparse_index = models.IntegerField(db_index=True)
43 | sparse_index_unique = models.IntegerField(db_index=True, unique=True)
44 | sparse_index_cmp_1 = models.IntegerField(db_index=True)
45 | sparse_index_cmp_2 = models.IntegerField(db_index=True)
46 |
47 | class MongoMeta:
48 | sparse_indexes = ['sparse_index', 'sparse_index_unique',
49 | ('sparse_index_cmp_1', 'sparse_index_cmp_2')]
50 | descending_indexes = ['descending_index',
51 | 'descending_index_custom_column']
52 | index_together = [
53 | {'fields': ['regular_index', 'custom_column']},
54 | {'fields': ('sparse_index_cmp_1', 'sparse_index_cmp_2')}]
55 |
56 |
57 | class IndexTestModel2(models.Model):
58 | a = models.IntegerField(db_index=True)
59 | b = models.IntegerField(db_index=True)
60 |
61 | class MongoMeta:
62 | index_together = ['a', ('b', -1)]
63 |
64 |
65 | class CustomColumnEmbeddedModel(models.Model):
66 | a = models.IntegerField(db_column='a2')
67 |
68 |
69 | class NewStyleIndexesTestModel(models.Model):
70 | f1 = models.IntegerField()
71 | f2 = models.IntegerField()
72 | f3 = models.IntegerField()
73 |
74 | db_index = models.IntegerField(db_index=True)
75 | unique = models.IntegerField(unique=True)
76 | custom_column = models.IntegerField(db_column='custom')
77 | geo = models.IntegerField()
78 | geo_custom_column = models.IntegerField(db_column='geo')
79 |
80 | dict1 = DictField()
81 | dict_custom_column = DictField(db_column='dict_custom')
82 | embedded = EmbeddedModelField(CustomColumnEmbeddedModel)
83 | embedded_list = ListField(EmbeddedModelField(CustomColumnEmbeddedModel))
84 |
85 | class Meta:
86 | unique_together = [('f2', 'custom_column'), ('f2', 'f3')]
87 |
88 | class MongoMeta:
89 | indexes = [
90 | [('f1', -1)],
91 | {'fields': 'f2', 'sparse': True},
92 | {'fields': [('custom_column', -1), 'f3']},
93 | [('geo', '2d')],
94 | {'fields': [('geo_custom_column', '2d'), 'f2'],
95 | 'min': 42, 'max': 21},
96 | {'fields': [('dict1.foo', 1)]},
97 | {'fields': [('dict_custom_column.foo', 1)]},
98 | {'fields': [('embedded.a', 1)]},
99 | {'fields': [('embedded_list.a', 1)]},
100 | ]
101 |
102 |
103 | class GridFSFieldTestModel(models.Model):
104 | gridfile = GridFSField()
105 | gridfile_nodelete = GridFSField(delete=False)
106 | gridfile_versioned = GridFSField(versioning=True)
107 | gridfile_versioned_delete = GridFSField(versioning=True, delete=True)
108 | gridstring = GridFSString()
109 |
110 |
111 | class Issue47Model(models.Model):
112 | foo = ListField(EmbeddedModelField(Post))
113 |
114 |
115 | class CustomIDModel(models.Model):
116 | id = models.IntegerField()
117 | primary = models.IntegerField(primary_key=True)
118 |
119 |
120 | class CustomIDModel2(models.Model):
121 | id = models.IntegerField(primary_key=True, db_column='blah')
122 |
123 |
124 | class CappedCollection(models.Model):
125 | n = models.IntegerField(default=42)
126 |
127 | class MongoMeta:
128 | capped = True
129 | collection_size = 10
130 |
131 |
132 | class CappedCollection2(models.Model):
133 |
134 | class MongoMeta:
135 | capped = True
136 | collection_size = 1000
137 | collection_max = 2
138 |
139 |
140 | class CappedCollection3(models.Model):
141 | n = models.IntegerField(default=43)
142 |
143 | class MongoMeta:
144 | capped = True
145 | collection_size = 1000
146 |
--------------------------------------------------------------------------------
/docs/source/topics/mapreduce.rst:
--------------------------------------------------------------------------------
1 | Map/Reduce
2 | ==========
3 |
4 | Map/Reduce, originally invented at Google, is a simple but powerful technology
5 | to efficiently process big amounts of data in parallel.
6 |
7 | For this, your processing logic must be split into two phases, the *map* and the
8 | *reduce* phase.
9 |
10 | The *map phase* takes all the input you'd like to process (in terms of MongoDB,
11 | this input are your *documents*) and emits one or more *key-value pairs* for
12 | each data record (it "maps" records to key-value pairs).
13 |
14 | The *reduce phase* "reduces" that set of key-value pairs into a single value.
15 |
16 | This document explains how to use `MongoDB's Map/Reduce functionality`_
17 | with Django models.
18 |
19 | .. warning::
20 |
21 | MongoDB's Map/Reduce is designed for one-time operations, i.e. it's *not*
22 | intended to be used in code that is executed on a regular basis
23 | (views, business logic, ...).
24 |
25 | .. currentmodule:: django_mongodb_engine.contrib
26 |
27 | How to Use It
28 | -------------
29 | Map/Reduce support for Django models is provided through Django MongoDB Engine's
30 | :class:`custom Manager `
31 | (:class:`What is a manager? `). ::
32 |
33 | from django_mongodb_engine.contrib import MongoDBManager
34 |
35 | class MapReduceableModel(models.Model):
36 | ...
37 | objects = MongoDBManager()
38 |
39 | The :class:`MongoDBManager` provides a :meth:`~MongoDBManager.map_reduce` method
40 | that has the same API as PyMongo's :meth:`~pymongo.collection.Collection.map_reduce`
41 | method (with the one exception that it adds a `drop_collection` option). ::
42 |
43 | >>> MapReduceableModel.objects.map_reduce(mapfunc, reducefunc, output_collection, ...)
44 |
45 | For very small result sets, you can also use in-memory Map/Reduce::
46 |
47 | >>> MapReducableModel.objects.inline_map_reduce(mapfunc, reducefunc, ...)
48 |
49 | It's also possible to run Map/Reduce against a subset of documents in the database::
50 |
51 | >>> MapReduceableModel.objects.filter(...).map_reduce(...)
52 |
53 | Both the map and the reduce function are written in Javascript.
54 |
55 | :meth:`~MongoDBManager.map_reduce` returns an iterator yielding
56 | :class:`MapReduceResult` objects.
57 |
58 | Special Reduce Function Rules
59 | -----------------------------
60 | A sane reduce function must be both associative and commutative -- that is,
61 | in terms of MongoDB, the following conditions must hold true::
62 |
63 | # Value order does not matter:
64 | reduce(k, [A, B]) == reduce(k, [B, A])
65 | # Values may itself be results of other reduce operations:
66 | reduce(k, [reduce(k, ...)]) == reduce(k, ...)
67 |
68 | This is because in order to be able to process in parallel, the reduce phase
69 | is split into several sub-phases, reducing parts of the map output and eventually
70 | merging them together into one grand total.
71 |
72 | Example
73 | -------
74 | (See also :ref:`the example in the tutorial ` and
75 | `Wikipedia `_, from which I stole the
76 | idea for the example beneath.)
77 |
78 | As an example, we'll count the number of occurrences of each word in a bunch of
79 | articles. Our models could look somewhat like this:
80 |
81 | .. literalinclude:: ../code/mapreduce/mr/models.py
82 | :start-after: models
83 | :end-before: class Author
84 |
85 | Our map function emits a ``(word, 1)`` pair for each word in an article's text
86 | (In the map function, `this` always refers to the current document).
87 |
88 | .. code-block:: js
89 |
90 | function() {
91 | this.text.split(' ').forEach(
92 | function(word) { emit(word, 1) }
93 | )
94 | }
95 |
96 | For an input text of "Django is named after Django Reinhardt", this would emit
97 | the following key-value pairs::
98 |
99 | Django : 1
100 | is : 1
101 | named : 1
102 | after : 1
103 | Django : 1
104 | Reinhardt : 1
105 |
106 | This pairs are now combined in such way that no key duplicates are left. ::
107 |
108 | is : [1]
109 | named : [1]
110 | after : [1]
111 | Django : [1, 1]
112 | Reinhardt : [1]
113 |
114 | To further process these pairs, we let our reduce function sum up all
115 | occurrences of each word
116 |
117 | .. code-block:: js
118 |
119 | function reduce(key, values) {
120 | return values.length; /* == sum(values) */
121 | }
122 |
123 | so that the final result is a list of key-"sum"-pairs::
124 |
125 | is : 1
126 | named : 1
127 | after : 1
128 | Django : 2
129 | Reinhardt : 1
130 |
131 | Show Me the Codes
132 | -----------------
133 | Here's a full example, using the models and functions described above, on how to
134 | use Django MongoDB Engine's Map/Reduce API.
135 |
136 | .. literalinclude:: ../code/mapreduce/mr/models.py
137 |
138 | .. literalinclude:: ../code/mapreduce/mr/tests.py
139 | :end-before: __test__
140 |
141 | .. literalinclude:: ../code/mapreduce/mr/tests.py
142 | :start-after: mr
143 | :end-before: """
144 |
145 |
146 | .. _MongoDB's Map/Reduce functionality: http://docs.mongodb.org/manual/core/map-reduce/
147 |
--------------------------------------------------------------------------------
/docs/source/topics/lowerlevel.rst:
--------------------------------------------------------------------------------
1 | Lower-Level Operations
2 | ======================
3 |
4 | When you hit the limit of what's possible with Django's ORM, you can always go
5 | down one abstraction layer to PyMongo_.
6 |
7 | You can use :ref:`raw queries and updates `
8 | to update or query for model instances using raw Mongo queries, bypassing
9 | Django's model query APIs.
10 |
11 | If that isn't enough, you can skip the model layer entirely and operate on
12 | :ref:`PyMongo-level objects `.
13 |
14 | .. warning::
15 |
16 | These APIs are available for MongoDB only, so using any of these features
17 | breaks portability to other non-relational databases (Google App Engine,
18 | Cassandra, Redis, ...). For the sake of portability you should try to avoid
19 | database-specific features whenever possible.
20 |
21 |
22 | .. _lowerlevel/raw-queries-and-updates:
23 |
24 | Raw Queries and Updates
25 | -----------------------
26 | .. currentmodule:: django_mongodb_engine.contrib
27 |
28 | :class:`MongoDBManager` provides two methods, :meth:`~MongoDBManager.raw_query`
29 | and :meth:`~MongoDBManager.raw_update`, that let you perform raw Mongo queries.
30 |
31 | .. note::
32 |
33 | When writing raw queries, please keep in mind that no field name substitution
34 | will be done, meaning that you'll always have to use database-level names --
35 | e.g. `_id` instead of `id` or `foo_id` instead of `foo` for foreignkeys.
36 |
37 | Raw Queries
38 | ...........
39 | :meth:`~MongoDBManager.raw_query` takes one argument, the Mongo query to execute,
40 | and returns a standard Django queryset -- which means that it also supports
41 | indexing and further manipulation.
42 |
43 | As an example, let's do some `Geo querying`_. ::
44 |
45 | from djangotoolbox.fields import EmbeddedModelField
46 | from django_mongodb_engine.contrib import MongoDBManager
47 |
48 | class Point(models.Model):
49 | latitude = models.FloatField()
50 | longtitude = models.FloatField()
51 |
52 | class Place(models.Model):
53 | ...
54 | location = EmbeddedModelField(Point)
55 |
56 | objects = MongoDBManager()
57 |
58 | To find all places near to your current location, 42°N | π°E,
59 | you can use this raw query::
60 |
61 | >>> here = {'latitude' : 42, 'longtitude' : 3.14}
62 | >>> Place.objects.raw_query({'location' : {'$near' : here}})
63 |
64 | As stated above, :meth:`~MongoDBManager.raw_query` returns a standard Django
65 | queryset, for which reason you can have even more fun with raw queries:
66 |
67 | .. code-block:: pycon
68 |
69 | Limit the number of results to 10
70 | >>> Foo.objects.raw_query({'location' : ...})[:10]
71 |
72 | Keep track of most interesting places
73 | >>> Foo.objects.raw_query({'location' : ...) \
74 | ... .update(interest=F('interest')+1)
75 |
76 | and whatnot.
77 |
78 | Raw Updates
79 | ...........
80 | :meth:`~MongoDBManager.raw_update` comes into play when Django MongoDB Engine's
81 | atomic updates through ``$set`` and ``$inc`` (using F_)
82 | are not powerful enough.
83 |
84 | The first argument is the query which describes the subset of documents the
85 | update should be executed against - as :class:`~django.db.models.Q` object or
86 | Mongo query. The second argument is the update spec.
87 |
88 | Consider this model::
89 |
90 | from django_mongodb_engine.contrib import MongoDBManager
91 |
92 | class FancyNumbers(models.Model):
93 | foo = models.IntegerField()
94 |
95 | objects = MongoDBManager()
96 |
97 | Let's do some of those super-cool MongoDB in-place bitwise operations. ::
98 |
99 | FancyNumbers.objects.raw_update({}, {'$bit' : {'foo' : {'or' : 42}}})
100 |
101 | That bitwise-ORs every `foo` of all documents in the database with 42.
102 |
103 | To run that update against a subset of the documents, for example against any
104 | whose `foo` is greater than π, use a non-empty filter condition::
105 |
106 | FancyNumbers.objects.raw_update(Q(foo__gt=3.14), {'$bit' : ...})
107 | # or
108 | FancyNumbers.objects.raw_update({'foo' : {'$gt' : 3.14}}, {'$bit' : ...})
109 |
110 | .. _lowerlevel/pymongo:
111 |
112 | PyMongo-level
113 | -------------
114 | :attr:`django.db.connections` is a dictionary-like object that holds all
115 | database connections -- that is, for MongoDB databases,
116 | :class:`django_mongodb_engine.base.DatabaseWrapper` instances.
117 |
118 | These instances can be used to get the PyMongo-level :class:`~pymongo.Connection`,
119 | :class:`~pymongo.database.Database` and :class:`~pymongo.collection.Collection`
120 | objects.
121 |
122 | For example, to execute a :meth:`~pymongo.collection.Collection.find_and_modify`
123 | command, you could use code similar to this::
124 |
125 | from django.db import connections
126 | database_wrapper = connections['my_db_alias']
127 | eggs_collection = database_wrapper.get_collection('eggs')
128 | eggs_collection.find_and_modify(...)
129 |
130 | .. _PyMongo: http://api.mongodb.org/python/current/
131 | .. _Geo querying: http://docs.mongodb.org/manual/core/geospatial-indexes/
132 | .. _F: https://docs.djangoproject.com/en/dev/topics/db/queries/#query-expressions
133 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | REM Command file for Sphinx documentation
4 |
5 | if "%SPHINXBUILD%" == "" (
6 | set SPHINXBUILD=sphinx-build
7 | )
8 | set BUILDDIR=_build
9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
10 | if NOT "%PAPER%" == "" (
11 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
12 | )
13 |
14 | if "%1" == "" goto help
15 |
16 | if "%1" == "help" (
17 | :help
18 | echo.Please use `make ^` where ^ is one of
19 | echo. html to make standalone HTML files
20 | echo. dirhtml to make HTML files named index.html in directories
21 | echo. singlehtml to make a single large HTML file
22 | echo. pickle to make pickle files
23 | echo. json to make JSON files
24 | echo. htmlhelp to make HTML files and a HTML help project
25 | echo. qthelp to make HTML files and a qthelp project
26 | echo. devhelp to make HTML files and a Devhelp project
27 | echo. epub to make an epub
28 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
29 | echo. text to make text files
30 | echo. man to make manual pages
31 | echo. changes to make an overview over all changed/added/deprecated items
32 | echo. linkcheck to check all external links for integrity
33 | echo. doctest to run all doctests embedded in the documentation if enabled
34 | goto end
35 | )
36 |
37 | if "%1" == "clean" (
38 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
39 | del /q /s %BUILDDIR%\*
40 | goto end
41 | )
42 |
43 | if "%1" == "html" (
44 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
45 | if errorlevel 1 exit /b 1
46 | echo.
47 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
48 | goto end
49 | )
50 |
51 | if "%1" == "dirhtml" (
52 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
53 | if errorlevel 1 exit /b 1
54 | echo.
55 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
56 | goto end
57 | )
58 |
59 | if "%1" == "singlehtml" (
60 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
61 | if errorlevel 1 exit /b 1
62 | echo.
63 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
64 | goto end
65 | )
66 |
67 | if "%1" == "pickle" (
68 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
69 | if errorlevel 1 exit /b 1
70 | echo.
71 | echo.Build finished; now you can process the pickle files.
72 | goto end
73 | )
74 |
75 | if "%1" == "json" (
76 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
77 | if errorlevel 1 exit /b 1
78 | echo.
79 | echo.Build finished; now you can process the JSON files.
80 | goto end
81 | )
82 |
83 | if "%1" == "htmlhelp" (
84 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
85 | if errorlevel 1 exit /b 1
86 | echo.
87 | echo.Build finished; now you can run HTML Help Workshop with the ^
88 | .hhp project file in %BUILDDIR%/htmlhelp.
89 | goto end
90 | )
91 |
92 | if "%1" == "qthelp" (
93 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
94 | if errorlevel 1 exit /b 1
95 | echo.
96 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
97 | .qhcp project file in %BUILDDIR%/qthelp, like this:
98 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\DjangoMongoDBEngine.qhcp
99 | echo.To view the help file:
100 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\DjangoMongoDBEngine.ghc
101 | goto end
102 | )
103 |
104 | if "%1" == "devhelp" (
105 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
106 | if errorlevel 1 exit /b 1
107 | echo.
108 | echo.Build finished.
109 | goto end
110 | )
111 |
112 | if "%1" == "epub" (
113 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
114 | if errorlevel 1 exit /b 1
115 | echo.
116 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
117 | goto end
118 | )
119 |
120 | if "%1" == "latex" (
121 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
122 | if errorlevel 1 exit /b 1
123 | echo.
124 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
125 | goto end
126 | )
127 |
128 | if "%1" == "text" (
129 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
130 | if errorlevel 1 exit /b 1
131 | echo.
132 | echo.Build finished. The text files are in %BUILDDIR%/text.
133 | goto end
134 | )
135 |
136 | if "%1" == "man" (
137 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
138 | if errorlevel 1 exit /b 1
139 | echo.
140 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
141 | goto end
142 | )
143 |
144 | if "%1" == "changes" (
145 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
146 | if errorlevel 1 exit /b 1
147 | echo.
148 | echo.The overview file is in %BUILDDIR%/changes.
149 | goto end
150 | )
151 |
152 | if "%1" == "linkcheck" (
153 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
154 | if errorlevel 1 exit /b 1
155 | echo.
156 | echo.Link check complete; look for any errors in the above output ^
157 | or in %BUILDDIR%/linkcheck/output.txt.
158 | goto end
159 | )
160 |
161 | if "%1" == "doctest" (
162 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
163 | if errorlevel 1 exit /b 1
164 | echo.
165 | echo.Testing of doctests in the sources finished, look at the ^
166 | results in %BUILDDIR%/doctest/output.txt.
167 | goto end
168 | )
169 |
170 | :end
171 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SOURCEDIR = source/
6 | SPHINXOPTS =
7 | SPHINXBUILD = $(ENV) sphinx-build
8 | PAPER =
9 | BUILDDIR = build
10 | ENV = PYTHONPATH=. \
11 | DJANGO_SETTINGS_MODULE=empty_project.settings
12 |
13 | # Internal variables.
14 | PAPEROPT_a4 = -D latex_paper_size=a4
15 | PAPEROPT_letter = -D latex_paper_size=letter
16 | ALLSPHINXOPTS = -d .doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SOURCEDIR)
17 |
18 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
19 |
20 | dev:
21 | rm -f build/html/_static/*css
22 | make html
23 | sh -c 'cd build/html/_static/&&rm mongodb.css&&ln -s ../../../source/mongodbtheme/static/mongodb.css .'
24 |
25 | help:
26 | @echo "Please use \`make ' where is one of"
27 | @echo " html to make standalone HTML files"
28 | @echo " dirhtml to make HTML files named index.html in directories"
29 | @echo " singlehtml to make a single large HTML file"
30 | @echo " pickle to make pickle files"
31 | @echo " json to make JSON files"
32 | @echo " htmlhelp to make HTML files and a HTML help project"
33 | @echo " qthelp to make HTML files and a qthelp project"
34 | @echo " devhelp to make HTML files and a Devhelp project"
35 | @echo " epub to make an epub"
36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
37 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
38 | @echo " text to make text files"
39 | @echo " man to make manual pages"
40 | @echo " changes to make an overview of all changed/added/deprecated items"
41 | @echo " linkcheck to check all external links for integrity"
42 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
43 |
44 | clean:
45 | -rm -rf $(BUILDDIR)/*
46 |
47 | html:
48 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
49 | @echo
50 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
51 |
52 | dirhtml:
53 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
54 | @echo
55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
56 |
57 | singlehtml:
58 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
59 | @echo
60 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
61 |
62 | pickle:
63 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
64 | @echo
65 | @echo "Build finished; now you can process the pickle files."
66 |
67 | json:
68 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
69 | @echo
70 | @echo "Build finished; now you can process the JSON files."
71 |
72 | htmlhelp:
73 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
74 | @echo
75 | @echo "Build finished; now you can run HTML Help Workshop with the" \
76 | ".hhp project file in $(BUILDDIR)/htmlhelp."
77 |
78 | qthelp:
79 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
80 | @echo
81 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
82 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
83 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/DjangoMongoDBEngine.qhcp"
84 | @echo "To view the help file:"
85 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/DjangoMongoDBEngine.qhc"
86 |
87 | devhelp:
88 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
89 | @echo
90 | @echo "Build finished."
91 | @echo "To view the help file:"
92 | @echo "# mkdir -p $$HOME/.local/share/devhelp/DjangoMongoDBEngine"
93 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/DjangoMongoDBEngine"
94 | @echo "# devhelp"
95 |
96 | epub:
97 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
98 | @echo
99 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
100 |
101 | latex:
102 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
103 | @echo
104 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
105 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
106 | "(use \`make latexpdf' here to do that automatically)."
107 |
108 | latexpdf:
109 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
110 | @echo "Running LaTeX files through pdflatex..."
111 | make -C $(BUILDDIR)/latex all-pdf
112 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
113 |
114 | text:
115 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
116 | @echo
117 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
118 |
119 | man:
120 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
121 | @echo
122 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
123 |
124 | changes:
125 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
126 | @echo
127 | @echo "The overview file is in $(BUILDDIR)/changes."
128 |
129 | linkcheck:
130 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
131 | @echo
132 | @echo "Link check complete; look for any errors in the above output " \
133 | "or in $(BUILDDIR)/linkcheck/output.txt."
134 |
135 | doctest:
136 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
137 | @echo "Testing of doctests in the sources finished, look at the " \
138 | "results in $(BUILDDIR)/doctest/output.txt."
139 |
--------------------------------------------------------------------------------
/django_mongodb_engine/storage.py:
--------------------------------------------------------------------------------
1 | import os
2 | import urlparse
3 |
4 | from django.core.exceptions import ImproperlyConfigured
5 | from django.core.files.storage import Storage
6 | from django.utils.encoding import filepath_to_uri
7 |
8 | from gridfs import GridFS, NoFile
9 |
10 |
11 | def _get_subcollections(collection):
12 | """
13 | Returns all sub-collections of `collection`.
14 | """
15 | # XXX: Use the MongoDB API for this once it exists.
16 | for name in collection.database.collection_names():
17 | cleaned = name[:name.rfind('.')]
18 | if cleaned != collection.name and cleaned.startswith(collection.name):
19 | yield cleaned
20 |
21 |
22 | class GridFSStorage(Storage):
23 | """
24 | GridFS Storage backend for Django.
25 |
26 | This backend aims to add a GridFS storage to upload files to
27 | using Django's file fields.
28 |
29 | For performance, the file hirarchy is represented as a tree of
30 | MongoDB sub-collections.
31 |
32 | (One could use a flat list, but to list a directory '/this/path/'
33 | we would have to execute a search over the whole collection and
34 | then filter the results to exclude those not starting by
35 | '/this/path' using that model.)
36 |
37 | :param location:
38 | (optional) Name of the top-level node that holds the files. This
39 | value of `location` is prepended to all file paths, so it works
40 | like the `location` setting for Django's built-in
41 | :class:`~django.core.files.storage.FileSystemStorage`.
42 | :param collection:
43 | Name of the collection the file tree shall be stored in.
44 | Defaults to 'storage'.
45 | :param database:
46 | Alias of the Django database to use. Defaults to 'default' (the
47 | default Django database).
48 | :param base_url:
49 | URL that serves the files in GridFS (for instance, through
50 | nginx-gridfs).
51 | Defaults to None (file not accessible through a URL).
52 | """
53 |
54 | def __init__(self, location='', collection='storage', database='default',
55 | base_url=None):
56 | self.location = location.strip(os.sep)
57 | self.collection = collection
58 | self.database = database
59 | self.base_url = base_url
60 |
61 | if not self.collection:
62 | raise ImproperlyConfigured("'collection' may not be empty.")
63 |
64 | if self.base_url and not self.base_url.endswith('/'):
65 | raise ImproperlyConfigured("If set, 'base_url' must end with a "
66 | "slash.")
67 |
68 | def _open(self, path, mode='rb'):
69 | """
70 | Returns a :class:`~gridfs.GridOut` file opened in `mode`, or
71 | raises :exc:`~gridfs.errors.NoFile` if the requested file
72 | doesn't exist and mode is not 'w'.
73 | """
74 | gridfs, filename = self._get_gridfs(path)
75 | try:
76 | return gridfs.get_last_version(filename)
77 | except NoFile:
78 | if 'w' in mode:
79 | return gridfs.new_file(filename=filename)
80 | else:
81 | raise
82 |
83 | def _save(self, path, content):
84 | """
85 | Saves `content` into the file at `path`.
86 | """
87 | gridfs, filename = self._get_gridfs(path)
88 | gridfs.put(content, filename=filename)
89 | return path
90 |
91 | def delete(self, path):
92 | """
93 | Deletes the file at `path` if it exists.
94 | """
95 | gridfs, filename = self._get_gridfs(path)
96 | try:
97 | gridfs.delete(gridfs.get_last_version(filename=filename)._id)
98 | except NoFile:
99 | pass
100 |
101 | def exists(self, path):
102 | """
103 | Returns `True` if the file at `path` exists in GridFS.
104 | """
105 | gridfs, filename = self._get_gridfs(path)
106 | return gridfs.exists(filename=filename)
107 |
108 | def listdir(self, path):
109 | """
110 | Returns a tuple (folders, lists) that are contained in the
111 | folder `path`.
112 | """
113 | gridfs, filename = self._get_gridfs(path)
114 | assert not filename
115 | subcollections = _get_subcollections(gridfs._GridFS__collection)
116 | return set(c.split('.')[-1] for c in subcollections), gridfs.list()
117 |
118 | def size(self, path):
119 | """
120 | Returns the size of the file at `path`.
121 | """
122 | gridfs, filename = self._get_gridfs(path)
123 | return gridfs.get_last_version(filename=filename).length
124 |
125 | def url(self, name):
126 | if self.base_url is None:
127 | raise ValueError("This file is not accessible via a URL.")
128 | return urlparse.urljoin(self.base_url, filepath_to_uri(name))
129 |
130 | def created_time(self, path):
131 | """
132 | Returns the datetime the file at `path` was created.
133 | """
134 | gridfs, filename = self._get_gridfs(path)
135 | return gridfs.get_last_version(filename=filename).upload_date
136 |
137 | def _get_gridfs(self, path):
138 | """
139 | Returns a :class:`~gridfs.GridFS` using the sub-collection for
140 | `path`.
141 | """
142 | path, filename = os.path.split(path)
143 | path = os.path.join(self.collection, self.location, path.strip(os.sep))
144 | collection_name = path.replace(os.sep, '.').strip('.')
145 |
146 | if not hasattr(self, '_db'):
147 | from django.db import connections
148 | self._db = connections[self.database].database
149 |
150 | return GridFS(self._db, collection_name), filename
151 |
--------------------------------------------------------------------------------
/tests/storage/tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from datetime import datetime, timedelta
3 | import os
4 | import tempfile
5 |
6 | from django.core.files.base import ContentFile, File
7 |
8 | from django_mongodb_engine.storage import GridFSStorage
9 |
10 | from .utils import TestCase
11 |
12 |
13 | class GridFSStorageTest(TestCase):
14 | storage_class = GridFSStorage
15 | temp_dir = tempfile.mktemp()
16 |
17 | def setUp(self):
18 | self.storage = self.get_storage(self.temp_dir)
19 |
20 | def tearDown(self):
21 | if hasattr(self.storage, '_db'):
22 | for collection in self.storage._db.collection_names():
23 | if not collection.startswith('system.'):
24 | self.storage._db.drop_collection(collection)
25 |
26 | def get_storage(self, location, **kwargs):
27 | return self.storage_class(location=location, **kwargs)
28 |
29 | def test_file_access_options(self):
30 | """
31 | Standard file access options are available, and work as
32 | expected.
33 | """
34 | self.assertFalse(self.storage.exists('storage_test'))
35 | f = self.storage.open('storage_test', 'w')
36 | f.write('storage contents')
37 | f.close()
38 | self.assert_(self.storage.exists('storage_test'))
39 |
40 | test_file = self.storage.open('storage_test', 'r')
41 | self.assertEqual(test_file.read(), 'storage contents')
42 |
43 | self.storage.delete('storage_test')
44 | self.assertFalse(self.storage.exists('storage_test'))
45 |
46 | # def test_file_accessed_time(self):
47 | # """
48 | # File storage returns a Datetime object for the last accessed
49 | # time of a file.
50 | # """
51 | # self.assertFalse(self.storage.exists('test.file'))
52 | #
53 | # f = ContentFile('custom contents')
54 | # f_name = self.storage.save('test.file', f)
55 | # atime = self.storage.accessed_time(f_name)
56 | #
57 | # self.assertEqual(atime, datetime.fromtimestamp(
58 | # os.path.getatime(self.storage.path(f_name))))
59 | # self.assertTrue(datetime.now() - self.storage.accessed_time(f_name) <
60 | # timedelta(seconds=2))
61 | # self.storage.delete(f_name)
62 |
63 | def test_file_created_time(self):
64 | """
65 | File storage returns a Datetime object for the creation time of
66 | a file.
67 | """
68 | self.assertFalse(self.storage.exists('test.file'))
69 |
70 | f = ContentFile('custom contents')
71 | f_name = self.storage.save('test.file', f)
72 | ctime = self.storage.created_time(f_name)
73 |
74 | self.assertTrue(datetime.now() - self.storage.created_time(f_name) <
75 | timedelta(seconds=2))
76 | self.storage.delete(f_name)
77 |
78 | # def test_file_modified_time(self):
79 | # """
80 | # File storage returns a Datetime object for the last modified
81 | # time of a file.
82 | # """
83 | # self.assertFalse(self.storage.exists('test.file'))
84 | #
85 | # f = ContentFile('custom contents')
86 | # f_name = self.storage.save('test.file', f)
87 | # mtime = self.storage.modified_time(f_name)
88 | #
89 | # self.assertTrue(datetime.now() - self.storage.modified_time(f_name) <
90 | # timedelta(seconds=2))
91 | #
92 | # self.storage.delete(f_name)
93 |
94 | def test_file_save_without_name(self):
95 | """
96 | File storage extracts the filename from the content object if
97 | no name is given explicitly.
98 | """
99 | self.assertFalse(self.storage.exists('test.file'))
100 |
101 | f = ContentFile('custom contents')
102 | f.name = 'test.file'
103 |
104 | storage_f_name = self.storage.save(None, f)
105 |
106 | self.assertEqual(storage_f_name, f.name)
107 |
108 | self.storage.delete(storage_f_name)
109 |
110 | # def test_file_path(self):
111 | # """
112 | # File storage returns the full path of a file
113 | # """
114 | # self.assertFalse(self.storage.exists('test.file'))
115 | #
116 | # f = ContentFile('custom contents')
117 | # f_name = self.storage.save('test.file', f)
118 | #
119 | # self.assertEqual(self.storage.path(f_name),
120 | # os.path.join(self.temp_dir, f_name))
121 | #
122 | # self.storage.delete(f_name)
123 |
124 | def test_file_url(self):
125 | """
126 | File storage returns a url to access a given file from the Web.
127 | """
128 | self.assertRaises(ValueError, self.storage.url, 'test.file')
129 |
130 | self.storage = self.get_storage(self.storage.location,
131 | base_url='foo/')
132 | self.assertEqual(self.storage.url('test.file'),
133 | '%s%s' % (self.storage.base_url, 'test.file'))
134 |
135 | def test_listdir(self):
136 | """
137 | File storage returns a tuple containing directories and files.
138 | """
139 | self.assertEqual(self.storage.listdir(''), (set(), []))
140 | self.assertFalse(self.storage.exists('storage_test_1'))
141 | self.assertFalse(self.storage.exists('storage_test_2'))
142 | self.assertFalse(self.storage.exists('storage_dir_1'))
143 |
144 | self.storage.save('storage_test_1', ContentFile('custom content'))
145 | self.storage.save('storage_test_2', ContentFile('custom content'))
146 | storage = self.get_storage(location=os.path.join(self.temp_dir,
147 | 'storage_dir_1'))
148 | storage.save('storage_test_3', ContentFile('custom content'))
149 |
150 | dirs, files = self.storage.listdir('')
151 | self.assertEqual(set(dirs), set([u'storage_dir_1']))
152 | self.assertEqual(set(files),
153 | set([u'storage_test_1', u'storage_test_2']))
154 |
155 |
156 | class GridFSStorageTestWithoutLocation(GridFSStorageTest):
157 | # Now test everything without passing a location argument.
158 | temp_dir = ''
159 |
--------------------------------------------------------------------------------
/docs/source/mongodbtheme/static/mongodb.css:
--------------------------------------------------------------------------------
1 | /* Fonts */
2 | @font-face { font-family: fallback_Qlassik; src: url('Qlassik_TB.otf'); }
3 | @font-face { font-family: fallback_Qlassik; font-weight: bold; src: url('QlassikBold_TB.otf'); }
4 |
5 |
6 | body {
7 | margin: 0;
8 | color: #4c3a2c;
9 | background-color: #f6f7ee;
10 | font-family: sans-serif;
11 | }
12 |
13 | a { text-decoration: none; color: #003366; }
14 | a:hover { border-bottom: 1px dotted #003366; }
15 | a[href^=http]:after {
16 | content: url(data:image/gif;base64,R0lGODlhEAAQAIABACBRZv///yH5BAEAAAEALAAAAAAQABAAAAIgjI+py+0GIngwKjkDvkhqXmWLaDXicXbgijLYRsVyXAAAOw%3D%3D);
17 | position: relative;
18 | top: 3px;
19 | opacity: 0.5;
20 | }
21 | a[href^=http]:hover:after { opacity: 0.7; }
22 |
23 | ul > li > ul {
24 | padding-left: 1.5em;
25 | }
26 |
27 | ul.simple > li,
28 | .toctree-wrapper > ul > li {
29 | padding-bottom: 0.3em;
30 | }
31 |
32 | .clearer { clear: both; }
33 |
34 | /*
35 | div, ul { border: 1px solid green; }
36 | */
37 |
38 |
39 | /* -------------------- Header -------------------- */
40 |
41 | #header {
42 | background-color: #442d22;
43 | font-family: Qlassik, 'Qlassik Medium', fallback_Qlassik, sans-serif;
44 | font-size: 13pt;
45 | height: 3.2em;
46 | }
47 | #header a {
48 | opacity: 0.7;
49 | border: 0;
50 | }
51 | #header li.current > a,
52 | #header a:hover {
53 | opacity: 1;
54 | }
55 |
56 | /* Navigation level 1 */
57 | #header > ul {
58 | margin: 0;
59 | padding: 0;
60 | position: relative;
61 | z-index: 1;
62 | top: 1.1em;
63 | }
64 | #header > ul > li {
65 | display: inline;
66 | }
67 | #header > ul > li > a {
68 | margin-left: -0.3em;
69 | padding: 1.1em 0.9em;
70 | padding-bottom: 1.2em;
71 | color: #fffdd4;
72 | }
73 | #header > ul > li:first-child a {
74 | padding-left: 1.5em;
75 | }
76 | #header > ul > li.current > a {
77 | font-weight: bold;
78 | }
79 |
80 | /* Navigation level 2 */
81 | #header > ul > li > ul {
82 | display: none;
83 | float: left;
84 | width: 100%;
85 | margin-top: 1.2em;
86 | margin-left: -0.7em;
87 | padding-left: 0.7em;
88 | background-color: #f6f4cd;
89 | border-bottom: 3px solid #fdfcf7;
90 | list-style-type: none;
91 | }
92 | #header > ul > li > ul > li { float: left; }
93 | #header > ul > li > ul > li > a {
94 | color: #403125;
95 | padding: 0.6em;
96 | padding-bottom: 0.5em;
97 | padding-left: 1.1em;
98 | display: block;
99 | }
100 | /*#header > ul > li > ul > li + li > a {
101 | padding-left: 0.6em;
102 | }*/
103 |
104 | /* Display sub-menus if activated */
105 | #header > ul > li.current > ul { display: block; }
106 |
107 | /* Top right caption */
108 | #header > a.logo {
109 | position: absolute;
110 | z-index: 1;
111 | top: 0;
112 | right: 0.2em;
113 | padding: 0.2em;
114 | font-size: 19.5pt;
115 | font-weight: bold;
116 | color: #f6f4cd;
117 | }
118 | #header > a.logo #slogan {
119 | color: #ccc;
120 | font-size: 12pt;
121 | margin-top: 1px;
122 | display: block;
123 | text-align: right;
124 | }
125 |
126 |
127 | /* ------------------- Content ------------------- */
128 |
129 | h1, h2, h3, h4, h5, h6 {
130 | font-weight: lighter;
131 | font-family: Qlassik, 'Qlassik Medium', fallback_Qlassik, sans-serif;
132 | }
133 | h1 { font-size: 26pt; margin-bottom: 0; }
134 | h1 a.headerlink { display: none; }
135 | .body > .section > .section ~ .section { margin-top: 2em; }
136 | h2 { font-size: 20pt; margin-bottom: 0.3em; }
137 | h2 tt { font-size: 18pt; }
138 | h2 + *, h3 + * { margin-top: 4px; }
139 | h3 { margin-bottom: 0; }
140 |
141 | /* headerlink = the pilcrow (¶) showed besides headlines */
142 | .headerlink {
143 | padding: 0 0.2em 0.2em;
144 | font-family: sans-serif;
145 | visibility: hidden;
146 | opacity: 0.2;
147 | border: 0 !important;
148 | }
149 | .headerlink:hover { opacity: 0.8; }
150 | *:hover > .headerlink { visibility: visible; }
151 |
152 | .bodywrapper { margin: 1.4em 0 0 250px; border-left: 1px solid #ccc; }
153 |
154 | /* Use some nice padding for the content */
155 | .bodywrapper { padding: 0.2em 1.8em 2em; }
156 |
157 | /* Indent paragraphs a tiny bit */
158 | .section > div { margin-left: 1px; }
159 | .section > p, dl { margin-left: 3px; }
160 |
161 | .section p, .section ul, .admonition, .section table,
162 | .sphinxsidebar p, .sphinxsidebar ul {
163 | font-size: 14px;
164 | line-height: 1.3;
165 | }
166 |
167 | /* No italic for internal references */
168 | .reference.internal em { font-style: normal; }
169 |
170 | dt tt { font-size: 110%; }
171 | dt .descname { font-size: 120%; font-weight: bold; }
172 | dt em { font-size: 90%; }
173 |
174 | dl.docutils dt { font-size: 110%; font-weight: bold; margin-bottom: -0.3em; }
175 | dl.docutils dd { margin-left: 1em; margin-bottom: 1.3em; }
176 |
177 | dl.class dd p { margin-top: 0.5em; margin-bottom: 0.5em; }
178 | dl.class dd p:first { margin-top: 0; }
179 |
180 | .document li {
181 | margin-bottom: 0.2em;
182 | }
183 |
184 | .field-name { padding-right: 0.5em; }
185 | .field-body > ul { margin-top: 1.8em; margin-left: -7.5em; }
186 | .field-body strong { font-family: monospace; }
187 |
188 | .admonition {
189 | border: 1px solid #ccc;
190 | padding: 0 1em;
191 | background-color: #eff0e7;
192 | }
193 | .admonition > * {
194 | margin-left: 0.2em;
195 | }
196 | .admonition .first {
197 | margin-top: 0.6em;
198 | margin-bottom: -0.5em;
199 | font-size: 13pt;
200 | margin-left: 0;
201 | }
202 | .warning {
203 | background-color: #f4e0d8;
204 | border-color: #f5c3bc;
205 | }
206 |
207 | .sphinxsidebar {
208 | position: relative;
209 | z-index: 1;
210 | float: left;
211 | width: 250px;
212 | height: 100%;
213 | margin-top: 1.3em;
214 | }
215 | .sphinxsidebarwrapper {
216 | padding: 1em;
217 | padding-top: 0;
218 | }
219 | .sphinxsidebarwrapper > ul {
220 | list-style-type: none;
221 | padding-left: 1px;
222 | }
223 |
224 | /* ------------------- Hacks ------------------- */
225 |
226 | /* Adjust headline position of pages without a toctree */
227 | #page-tutorial, #page-index, #page-troubleshooting {
228 | position: relative;
229 | top: -1.1em;
230 | }
231 | #page-tutorial .sphinxsidebar, #page-troubleshooting .sphinxsidebar {
232 | margin-top: 0;
233 | }
234 | #page-index .sphinxsidebar { display: none; }
235 | #page-index .bodywrapper { margin-left: 0; }
236 |
237 | .highlight, .highlight-python {
238 | background: #f1f2e9 !important;
239 | }
240 | :not(.highlight-python) > .highlight, .highlight-python {
241 | margin-top: 2px;
242 | margin-bottom: 2px;
243 | padding: 0 0.7em;
244 | border: 1px solid #ccc;
245 | border-left: 0;
246 | border-right: 0;
247 | color: #442d22;
248 | }
249 | .highlight .go { color: #5c5c5c !important; } /* Generic.Output */
250 | .highlight-none pre { white-space: pre-wrap; }
251 |
252 |
253 | /* ------------------- Footer ------------------- */
254 |
255 | .footer {
256 | clear: both;
257 | font-size: 12px;
258 | margin-bottom: 5px;
259 | text-align: center;
260 | color: #616161;
261 | }
262 |
--------------------------------------------------------------------------------
/django_mongodb_engine/fields.py:
--------------------------------------------------------------------------------
1 | from django.db import connections, models
2 |
3 | from gridfs import GridFS
4 | from gridfs.errors import NoFile
5 |
6 | # handle pymongo backward compatibility
7 | try:
8 | from bson.objectid import ObjectId
9 | except ImportError:
10 | from pymongo.objectid import ObjectId
11 |
12 |
13 | from django_mongodb_engine.utils import make_struct
14 |
15 |
16 | __all__ = ['GridFSField', 'GridFSString']
17 |
18 |
19 | class GridFSField(models.Field):
20 | """
21 | GridFS field to store large chunks of data (blobs) in GridFS.
22 |
23 | Model instances keep references (ObjectIds) to GridFS files
24 | (:class:`gridfs.GridOut`) which are fetched on first attribute
25 | access.
26 |
27 | :param delete:
28 | Whether to delete the data stored in the GridFS (as GridFS
29 | files) when model instances are deleted (default: :const:`True`).
30 |
31 | Note that this doesn't have any influence on what happens if
32 | you update the blob value by assigning a new file, in which
33 | case the old file is always deleted.
34 | """
35 | forbids_updates = True
36 |
37 | def __init__(self, *args, **kwargs):
38 | self._versioning = kwargs.pop('versioning', False)
39 | self._autodelete = kwargs.pop('delete', not self._versioning)
40 | if self._versioning:
41 | import warnings
42 | warnings.warn("GridFSField versioning will be deprecated on "
43 | "version 0.6. If you consider this option useful "
44 | "please add a comment on issue #65 with your use "
45 | "case.", PendingDeprecationWarning)
46 |
47 | kwargs['max_length'] = 24
48 | kwargs.setdefault('default', None)
49 | kwargs.setdefault('null', True)
50 | super(GridFSField, self).__init__(*args, **kwargs)
51 |
52 | def db_type(self, connection):
53 | return 'gridfs'
54 |
55 | def contribute_to_class(self, model, name):
56 | # GridFSFields are represented as properties in the model
57 | # class. Let 'foo' be an instance of a model that has the
58 | # GridFSField 'gridf'. 'foo.gridf' then calls '_property_get'
59 | # and 'foo.gridfs = bar' calls '_property_set(bar)'.
60 | super(GridFSField, self).contribute_to_class(model, name)
61 | setattr(model, self.attname, property(self._property_get,
62 | self._property_set))
63 | if self._autodelete:
64 | models.signals.pre_delete.connect(self._on_pre_delete,
65 | sender=model)
66 |
67 | def _property_get(self, model_instance):
68 | """
69 | Gets the file from GridFS using the id stored in the model.
70 | """
71 | meta = self._get_meta(model_instance)
72 | if meta.filelike is None and meta.oid is not None:
73 | gridfs = self._get_gridfs(model_instance)
74 | if self._versioning:
75 | try:
76 | meta.filelike = gridfs.get_last_version(filename=meta.oid)
77 | return meta.filelike
78 | except NoFile:
79 | pass
80 | meta.filelike = gridfs.get(meta.oid)
81 | return meta.filelike
82 |
83 | def _property_set(self, model_instance, value):
84 | """
85 | Sets a new value.
86 |
87 | If value is an ObjectID it must be coming from Django's ORM
88 | internals being the value fetched from the database on query.
89 | In that case just update the id stored in the model instance.
90 | Otherwise it sets the value and checks whether a save is needed
91 | or not.
92 | """
93 | meta = self._get_meta(model_instance)
94 | if isinstance(value, ObjectId) and meta.oid is None:
95 | meta.oid = value
96 | else:
97 | meta.should_save = meta.filelike != value
98 | meta.filelike = value
99 |
100 | def pre_save(self, model_instance, add):
101 | meta = self._get_meta(model_instance)
102 | if meta.should_save:
103 | gridfs = self._get_gridfs(model_instance)
104 | if not self._versioning and meta.oid is not None:
105 | # We're putting a new GridFS file, so get rid of the
106 | # old one if we weren't explicitly asked to keep it.
107 | gridfs.delete(meta.oid)
108 | meta.should_save = False
109 | if not self._versioning or meta.oid is None:
110 | meta.oid = gridfs.put(meta.filelike)
111 | else:
112 | gridfs.put(meta.filelike, filename=meta.oid)
113 | return meta.oid
114 |
115 | def _on_pre_delete(self, sender, instance, using, signal, **kwargs):
116 | """
117 | Deletes the files associated with this isntance.
118 |
119 | If versioning is enabled all versions will be deleted.
120 | """
121 | gridfs = self._get_gridfs(instance)
122 | meta = self._get_meta(instance)
123 | try:
124 | while self._versioning and meta.oid:
125 | last = gridfs.get_last_version(filename=meta.oid)
126 | gridfs.delete(last._id)
127 | except NoFile:
128 | pass
129 |
130 | gridfs.delete(meta.oid)
131 |
132 | def _get_meta(self, model_instance):
133 | meta_name = '_%s_meta' % self.attname
134 | meta = getattr(model_instance, meta_name, None)
135 | if meta is None:
136 | meta_cls = make_struct('filelike', 'oid', 'should_save')
137 | meta = meta_cls(None, None, None)
138 | setattr(model_instance, meta_name, meta)
139 | return meta
140 |
141 | def _get_gridfs(self, model_instance):
142 | model = model_instance.__class__
143 | return GridFS(connections[model.objects.db].database,
144 | model._meta.db_table)
145 |
146 |
147 | class GridFSString(GridFSField):
148 | """
149 | Similar to :class:`GridFSField`, but the data is represented as a
150 | bytestring on Python side. This implies that all data has to be
151 | copied **into memory**, so :class:`GridFSString` is for smaller
152 | chunks of data only.
153 | """
154 |
155 | def _property_get(self, model):
156 | filelike = super(GridFSString, self)._property_get(model)
157 | if filelike is None:
158 | return ''
159 | if hasattr(filelike, 'read'):
160 | return filelike.read()
161 | return filelike
162 |
163 |
164 | try:
165 | # Used to satisfy South when introspecting models that use
166 | # GridFSField/GridFSString fields. Custom rules could be added
167 | # if needed.
168 | from south.modelsinspector import add_introspection_rules
169 | add_introspection_rules(
170 | [], ['^django_mongodb_engine\.fields\.GridFSField'])
171 | add_introspection_rules(
172 | [], ['^django_mongodb_engine\.fields\.GridFSString'])
173 | except ImportError:
174 | pass
175 |
--------------------------------------------------------------------------------
/django_mongodb_engine/contrib/__init__.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | from django.db import models, connections
4 | from django.db.models.query import QuerySet
5 | from django.db.models.sql.query import Query as SQLQuery
6 |
7 |
8 | ON_PYPY = hasattr(sys, 'pypy_version_info')
9 |
10 |
11 | def _compiler_for_queryset(qs, which='SQLCompiler'):
12 | connection = connections[qs.db]
13 | Compiler = connection.ops.compiler(which)
14 | return Compiler(qs.query, connection, connection.alias)
15 |
16 |
17 | class RawQuery(SQLQuery):
18 |
19 | def __init__(self, model, raw_query):
20 | super(RawQuery, self).__init__(model)
21 | self.raw_query = raw_query
22 |
23 | def clone(self, *args, **kwargs):
24 | clone = super(RawQuery, self).clone(*args, **kwargs)
25 | clone.raw_query = self.raw_query
26 | return clone
27 |
28 |
29 | class RawQueryMixin:
30 |
31 | def get_raw_query_set(self, raw_query):
32 | return QuerySet(self.model, RawQuery(self.model, raw_query), self._db)
33 |
34 | def raw_query(self, query=None):
35 | """
36 | Does a raw MongoDB query. The optional parameter `query` is the spec
37 | passed to PyMongo's :meth:` pymongo.Collection.find`.
38 | """
39 | return self.get_raw_query_set(query or {})
40 |
41 | def raw_update(self, spec_or_q, update_dict, **kwargs):
42 | """
43 | Does a raw MongoDB update. `spec_or_q` is either a MongoDB
44 | filter dict or a :class:`~django.db.models.query_utils.Q`
45 | instance that selects the records to update. `update_dict` is
46 | a MongoDB style update document containing either a new
47 | document or atomic modifiers such as ``$inc``.
48 |
49 | Keyword arguments will be passed to :meth:`pymongo.Collection.update`.
50 | """
51 | if isinstance(spec_or_q, dict):
52 | queryset = self.get_raw_query_set(spec_or_q)
53 | else:
54 | queryset = self.filter(spec_or_q)
55 | queryset._for_write = True
56 | compiler = _compiler_for_queryset(queryset, 'SQLUpdateCompiler')
57 | compiler.execute_update(update_dict, **kwargs)
58 |
59 | raw_update.alters_data = True
60 |
61 |
62 | class MapReduceResult(object):
63 | """
64 | Represents one item of a MapReduce result array.
65 |
66 | :param model: the model on that query the MapReduce was performed
67 | :param key: the *key* from the result item
68 | :param value: the *value* from the result item
69 | """
70 |
71 | def __init__(self, model, key, value):
72 | self.model = model
73 | self.key = key
74 | self.value = value
75 |
76 | @classmethod
77 | def from_entity(cls, model, entity):
78 | return cls(model, entity['_id'], entity['value'])
79 |
80 | def __repr__(self):
81 | return '<%s model=%r key=%r value=%r>' % (self.__class__.__name__,
82 | self.model.__name__,
83 | self.key, self.value)
84 |
85 |
86 | class MongoDBQuerySet(QuerySet):
87 |
88 | def map_reduce(self, *args, **kwargs):
89 | """
90 | Performs a Map/Reduce operation on all documents matching the query,
91 | yielding a :class:`MapReduceResult` object for each result entity.
92 |
93 | If the optional keyword argument `drop_collection` is ``True``, the
94 | result collection will be dropped after fetching all results.
95 |
96 | Any other arguments are passed to :meth:`Collection.map_reduce
97 | `.
98 | """
99 | # TODO: Field name substitution (e.g. id -> _id).
100 | drop_collection = kwargs.pop('drop_collection', False)
101 | query = self._get_query()
102 | kwargs.setdefault('query', query.mongo_query)
103 | result_collection = query.collection.map_reduce(*args, **kwargs)
104 | # TODO: Get rid of this.
105 | # PyPy has no guaranteed garbage collection so we can't rely on
106 | # the 'finally' suite of a generator (_map_reduce_cpython) to
107 | # be executed in time (in fact, it isn't guaranteed to be
108 | # executed *at all*). On the other hand, we *must* drop the
109 | # collection if `drop_collection` is True so we can't use a
110 | # generator in this case.
111 | if drop_collection and ON_PYPY:
112 | return self._map_reduce_pypy_drop_collection_hack(
113 | result_collection)
114 | else:
115 | return self._map_reduce_cpython(result_collection,
116 | drop_collection)
117 |
118 | def _map_reduce_cpython(self, result_collection, drop_collection):
119 | try:
120 | for entity in result_collection.find():
121 | yield MapReduceResult.from_entity(self.model, entity)
122 | finally:
123 | if drop_collection:
124 | result_collection.drop()
125 |
126 | def _map_reduce_pypy_drop_collection_hack(self, result_collection):
127 | try:
128 | return iter([MapReduceResult.from_entity(self.model, entity)
129 | for entity in result_collection.find()])
130 | finally:
131 | result_collection.drop()
132 |
133 | def inline_map_reduce(self, *args, **kwargs):
134 | """
135 | Similar to :meth:`map_reduce` but runs the Map/Reduce in memory,
136 | returning a list of :class:`MapReduceResults `.
137 |
138 | Does not take the `drop_collection` keyword argument since no result
139 | collection is involved for in-memory Map/Reduce operations.
140 | """
141 | query = self._get_query()
142 | kwargs.setdefault('query', query.mongo_query)
143 | return [MapReduceResult.from_entity(self.model, entity) for entity in
144 | query.collection.inline_map_reduce(*args, **kwargs)]
145 |
146 | def _get_query(self):
147 | return _compiler_for_queryset(self).build_query()
148 |
149 | def distinct(self, *args, **kwargs):
150 | query = self._get_query()
151 | return query.get_cursor().distinct(*args, **kwargs)
152 |
153 |
154 | class MongoDBManager(models.Manager, RawQueryMixin):
155 | """
156 | Lets you use Map/Reduce and raw query/update with your models::
157 |
158 | class FooModel(models.Model):
159 | ...
160 | objects = MongoDBManager()
161 | """
162 |
163 | def map_reduce(self, *args, **kwargs):
164 | return self.get_query_set().map_reduce(*args, **kwargs)
165 |
166 | def inline_map_reduce(self, *args, **kwargs):
167 | return self.get_query_set().inline_map_reduce(*args, **kwargs)
168 |
169 | def get_query_set(self):
170 | return MongoDBQuerySet(self.model, using=self._db)
171 |
172 | def distinct(self, *args, **kwargs):
173 | """
174 | Runs a :meth:`~pymongo.Collection.distinct` query against the
175 | database.
176 | """
177 | return self.get_query_set().distinct(*args, **kwargs)
178 |
--------------------------------------------------------------------------------
/django_mongodb_engine/creation.py:
--------------------------------------------------------------------------------
1 | from django.db.utils import DatabaseError
2 |
3 | from pymongo import DESCENDING
4 |
5 | from djangotoolbox.db.creation import NonrelDatabaseCreation
6 |
7 | from .utils import make_index_list
8 |
9 |
10 | class DatabaseCreation(NonrelDatabaseCreation):
11 |
12 | # We'll store decimals as strings, dates and times as datetimes,
13 | # sets as lists and automatic keys as ObjectIds.
14 | data_types = dict(NonrelDatabaseCreation.data_types, **{
15 | 'SetField': 'list',
16 | })
17 |
18 | def db_type(self, field):
19 | """
20 | Returns the db_type of the field for non-relation fields, and
21 | the db_type of a primary key field of a related model for
22 | ForeignKeys, OneToOneFields and ManyToManyFields.
23 | """
24 | if field.rel is not None:
25 | field = field.rel.get_related_field()
26 | return field.db_type(connection=self.connection)
27 |
28 | def sql_indexes_for_model(self, model, termstyle):
29 | """Creates indexes for all fields in ``model``."""
30 | meta = model._meta
31 |
32 | if not meta.managed or meta.proxy:
33 | return []
34 |
35 | collection = self.connection.get_collection(meta.db_table)
36 |
37 | def ensure_index(*args, **kwargs):
38 | if ensure_index.first_index:
39 | print "Installing indices for %s.%s model." % \
40 | (meta.app_label, meta.object_name)
41 | ensure_index.first_index = False
42 | return collection.ensure_index(*args, **kwargs)
43 | ensure_index.first_index = True
44 |
45 | newstyle_indexes = getattr(meta, 'indexes', None)
46 | if newstyle_indexes:
47 | self._handle_newstyle_indexes(ensure_index, meta, newstyle_indexes)
48 | else:
49 | self._handle_oldstyle_indexes(ensure_index, meta)
50 |
51 | def _handle_newstyle_indexes(self, ensure_index, meta, indexes):
52 | from djangotoolbox.fields import AbstractIterableField, \
53 | EmbeddedModelField
54 |
55 | # Django indexes.
56 | for field in meta.local_fields:
57 | if not (field.unique or field.db_index):
58 | # field doesn't need an index.
59 | continue
60 | column = '_id' if field.primary_key else field.column
61 | ensure_index(column, unique=field.unique)
62 |
63 | # Django unique_together indexes.
64 | indexes = list(indexes)
65 |
66 | for fields in getattr(meta, 'unique_together', []):
67 | assert isinstance(fields, (list, tuple))
68 | indexes.append({'fields': make_index_list(fields), 'unique': True})
69 |
70 | def get_column_name(field):
71 | opts = meta
72 | parts = field.split('.')
73 | for i, part in enumerate(parts):
74 | field = opts.get_field(part)
75 | parts[i] = field.column
76 | if isinstance(field, AbstractIterableField):
77 | field = field.item_field
78 | if isinstance(field, EmbeddedModelField):
79 | opts = field.embedded_model._meta
80 | else:
81 | break
82 | return '.'.join(parts)
83 |
84 | for index in indexes:
85 | if isinstance(index, dict):
86 | kwargs = index.copy()
87 | fields = kwargs.pop('fields')
88 | else:
89 | fields, kwargs = index, {}
90 | fields = [(get_column_name(name), direction)
91 | for name, direction in make_index_list(fields)]
92 | ensure_index(fields, **kwargs)
93 |
94 | def _handle_oldstyle_indexes(self, ensure_index, meta):
95 | from warnings import warn
96 | warn("'descending_indexes', 'sparse_indexes' and 'index_together' "
97 | "are deprecated and will be ignored as of version 0.6. "
98 | "Use 'indexes' instead.", DeprecationWarning)
99 | sparse_indexes = []
100 | descending_indexes = set(getattr(meta, 'descending_indexes', ()))
101 |
102 | # Lets normalize the sparse_index values changing [], set() to ().
103 | for idx in set(getattr(meta, 'sparse_indexes', ())):
104 | sparse_indexes.append(
105 | isinstance(idx, (tuple, set, list)) and tuple(idx) or idx)
106 |
107 | # Ordinary indexes.
108 | for field in meta.local_fields:
109 | if not (field.unique or field.db_index):
110 | # field doesn't need an index.
111 | continue
112 | column = '_id' if field.primary_key else field.column
113 | if field.name in descending_indexes:
114 | column = [(column, DESCENDING)]
115 | ensure_index(column, unique=field.unique,
116 | sparse=field.name in sparse_indexes)
117 |
118 | def create_compound_indexes(indexes, **kwargs):
119 | # indexes: (field1, field2, ...).
120 | if not indexes:
121 | return
122 | kwargs['sparse'] = tuple(indexes) in sparse_indexes
123 | indexes = [(meta.get_field(name).column, direction) for
124 | name, direction in make_index_list(indexes)]
125 | ensure_index(indexes, **kwargs)
126 |
127 | # Django unique_together indexes.
128 | for indexes in getattr(meta, 'unique_together', []):
129 | assert isinstance(indexes, (list, tuple))
130 | create_compound_indexes(indexes, unique=True)
131 |
132 | # MongoDB compound indexes.
133 | index_together = getattr(meta, 'mongo_index_together', [])
134 | if index_together:
135 | if isinstance(index_together[0], dict):
136 | # Assume index_together = [{'fields' : [...], ...}].
137 | for args in index_together:
138 | kwargs = args.copy()
139 | create_compound_indexes(kwargs.pop('fields'), **kwargs)
140 | else:
141 | # Assume index_together = ['foo', 'bar', ('spam', -1), etc].
142 | create_compound_indexes(index_together)
143 |
144 | return []
145 |
146 | def sql_create_model(self, model, *unused):
147 | """
148 | Creates a collection that will store instances of the model.
149 |
150 | Technically we only need to precreate capped collections, but
151 | we'll create them for all models, so database introspection
152 | knows about empty "tables".
153 | """
154 | name = model._meta.db_table
155 | if getattr(model._meta, 'capped', False):
156 | kwargs = {'capped': True}
157 | size = getattr(model._meta, 'collection_size', None)
158 | if size is not None:
159 | kwargs['size'] = size
160 | max_ = getattr(model._meta, 'collection_max', None)
161 | if max_ is not None:
162 | kwargs['max'] = max_
163 | else:
164 | kwargs = {}
165 |
166 | collection = self.connection.get_collection(name, existing=True)
167 | if collection is not None:
168 | opts = dict(collection.options())
169 | if opts != kwargs:
170 | raise DatabaseError("Can't change options of an existing "
171 | "collection: %s --> %s." % (opts, kwargs))
172 |
173 | # Initialize the capped collection:
174 | self.connection.get_collection(name, **kwargs)
175 |
176 | return [], {}
177 |
178 | def set_autocommit(self):
179 | """There's no such thing in MongoDB."""
180 |
181 | def create_test_db(self, verbosity=1, autoclobber=False):
182 | """
183 | No need to create databases in MongoDB :)
184 | but we can make sure that if the database existed is emptied.
185 | """
186 | test_database_name = self._get_test_db_name()
187 |
188 | self.connection.settings_dict['NAME'] = test_database_name
189 | # This is important. Here we change the settings so that all
190 | # other code thinks that the chosen database is now the test
191 | # database. This means that nothing needs to change in the test
192 | # code for working with connections, databases and collections.
193 | # It will appear the same as when working with non-test code.
194 |
195 | # Force a reconnect to ensure we're using the test database.
196 | self.connection._reconnect()
197 |
198 | # In this phase it will only drop the database if it already
199 | # existed which could potentially happen if the test database
200 | # was created but was never dropped at the end of the tests.
201 | self._drop_database(test_database_name)
202 |
203 | from django.core.management import call_command
204 | call_command('syncdb', verbosity=max(verbosity-1, 0),
205 | interactive=False, database=self.connection.alias)
206 |
207 | return test_database_name
208 |
209 | def destroy_test_db(self, old_database_name, verbosity=1):
210 | if verbosity >= 1:
211 | print "Destroying test database for alias '%s'..." % \
212 | self.connection.alias
213 | test_database_name = self.connection.settings_dict['NAME']
214 | self._drop_database(test_database_name)
215 | self.connection.settings_dict['NAME'] = old_database_name
216 |
217 | def _drop_database(self, database_name):
218 | for collection in self.connection.introspection.table_names():
219 | if not collection.startswith('system.'):
220 | self.connection.database.drop_collection(collection)
221 |
--------------------------------------------------------------------------------
/tests/contrib/tests.py:
--------------------------------------------------------------------------------
1 | from __future__ import with_statement
2 |
3 | from functools import partial
4 |
5 | from django.db.models import Q
6 | from django.db.utils import DatabaseError
7 |
8 | from django_mongodb_engine.contrib import MapReduceResult
9 |
10 | from models import *
11 | from utils import TestCase, get_collection, skip
12 |
13 |
14 | class MapReduceTests(TestCase):
15 |
16 | def test_map_reduce(self, inline=False):
17 | mapfunc = """
18 | function map() {
19 | for(i=0; i 13:
118 | value = value[:10] + '...'
119 | msg = "AutoField (default primary key) values must be " \
120 | "strings representing an ObjectId on MongoDB (got " \
121 | "%r instead)." % value
122 | if field.model._meta.db_table == 'django_site':
123 | # Also provide some useful tips for (very common) issues
124 | # with settings.SITE_ID.
125 | msg += " Please make sure your SITE_ID contains a " \
126 | "valid ObjectId string."
127 | raise DatabaseError(msg)
128 |
129 | # PyMongo can only process datatimes?
130 | elif db_type == 'date':
131 | return datetime.datetime(value.year, value.month, value.day)
132 | elif db_type == 'time':
133 | return datetime.datetime(1, 1, 1, value.hour, value.minute,
134 | value.second, value.microsecond)
135 |
136 | return value
137 |
138 | def _value_from_db(self, value, field, field_kind, db_type):
139 | """
140 | Deconverts keys, dates and times (also in collections).
141 | """
142 |
143 | # It is *crucial* that this is written as a direct check --
144 | # when value is an instance of serializer.LazyModelInstance
145 | # calling its __eq__ method does a database query.
146 | if value is None:
147 | return None
148 |
149 | # All keys have been turned into ObjectIds.
150 | if db_type == 'key':
151 | value = unicode(value)
152 |
153 | # We've converted dates and times to datetimes.
154 | elif db_type == 'date':
155 | value = datetime.date(value.year, value.month, value.day)
156 | elif db_type == 'time':
157 | value = datetime.time(value.hour, value.minute, value.second,
158 | value.microsecond)
159 |
160 | # Revert the decimal-to-string encoding.
161 | if field_kind == 'DecimalField':
162 | value = decimal.Decimal(value)
163 |
164 | return super(DatabaseOperations, self)._value_from_db(
165 | value, field, field_kind, db_type)
166 |
167 |
168 | class DatabaseClient(NonrelDatabaseClient):
169 | pass
170 |
171 |
172 | class DatabaseValidation(NonrelDatabaseValidation):
173 | pass
174 |
175 |
176 | class DatabaseIntrospection(NonrelDatabaseIntrospection):
177 |
178 | def table_names(self, cursor=None):
179 | return self.connection.database.collection_names()
180 |
181 | def sequence_list(self):
182 | # Only required for backends that use integer primary keys.
183 | pass
184 |
185 |
186 | class DatabaseWrapper(NonrelDatabaseWrapper):
187 | """
188 | Public API: connection, database, get_collection.
189 | """
190 |
191 | def __init__(self, *args, **kwargs):
192 | self.collection_class = kwargs.pop('collection_class', Collection)
193 | super(DatabaseWrapper, self).__init__(*args, **kwargs)
194 |
195 | self.features = DatabaseFeatures(self)
196 | self.ops = DatabaseOperations(self)
197 | self.creation = DatabaseCreation(self)
198 | self.introspection = DatabaseIntrospection(self)
199 | self.validation = DatabaseValidation(self)
200 | self.connected = False
201 | del self.connection
202 |
203 | def get_collection(self, name, **kwargs):
204 | if (kwargs.pop('existing', False) and
205 | name not in self.database.collection_names()):
206 | return None
207 | collection = self.collection_class(self.database, name, **kwargs)
208 | if settings.DEBUG:
209 | collection = CollectionDebugWrapper(collection, self.alias)
210 | return collection
211 |
212 | def __getattr__(self, attr):
213 | if attr in ['connection', 'database']:
214 | assert not self.connected
215 | self._connect()
216 | return getattr(self, attr)
217 | raise AttributeError(attr)
218 |
219 | def _connect(self):
220 | settings = copy.deepcopy(self.settings_dict)
221 |
222 | def pop(name, default=None):
223 | return settings.pop(name) or default
224 |
225 | db_name = pop('NAME')
226 | host = pop('HOST')
227 | port = pop('PORT', 27017)
228 | user = pop('USER')
229 | password = pop('PASSWORD')
230 | options = pop('OPTIONS', {})
231 |
232 | self.operation_flags = options.pop('OPERATIONS', {})
233 | if not any(k in ['save', 'delete', 'update']
234 | for k in self.operation_flags):
235 | # Flags apply to all operations.
236 | flags = self.operation_flags
237 | self.operation_flags = {'save': flags, 'delete': flags,
238 | 'update': flags}
239 |
240 | # Lower-case all OPTIONS keys.
241 | for key in options.iterkeys():
242 | options[key.lower()] = options.pop(key)
243 |
244 | read_preference = options.get('read_preference')
245 | replicaset = options.get('replicaset')
246 |
247 | if not read_preference:
248 | read_preference = options.get('slave_okay', options.get('slaveok'))
249 | if read_preference:
250 | options['read_preference'] = ReadPreference.SECONDARY
251 | warnings.warn("slave_okay has been deprecated. "
252 | "Please use read_preference instead.")
253 |
254 | conn_options = dict(
255 | host=host,
256 | port=int(port),
257 | document_class=dict,
258 | tz_aware=False
259 | )
260 | conn_options.update(options)
261 |
262 | if replicaset:
263 | connection_class = MongoReplicaSetClient
264 | else:
265 | connection_class = MongoClient
266 |
267 | try:
268 | self.connection = connection_class(**conn_options)
269 | self.database = self.connection[db_name]
270 | except TypeError:
271 | exc_info = sys.exc_info()
272 | raise ImproperlyConfigured, exc_info[1], exc_info[2]
273 |
274 | if user and password:
275 | if not self.database.authenticate(user, password):
276 | raise ImproperlyConfigured("Invalid username or password.")
277 |
278 | self.connected = True
279 | connection_created.send(sender=self.__class__, connection=self)
280 |
281 | def _reconnect(self):
282 | if self.connected:
283 | del self.connection
284 | del self.database
285 | self.connected = False
286 | self._connect()
287 |
288 | def _commit(self):
289 | pass
290 |
291 | def _rollback(self):
292 | pass
293 |
294 | def close(self):
295 | pass
296 |
--------------------------------------------------------------------------------