Compare commits
46 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
aa2add39ad | ||
|
a928047147 | ||
|
88dc64653e | ||
|
5f4b70f3a9 | ||
|
51b429e5b0 | ||
|
360624eb6e | ||
|
d9d2291837 | ||
|
cbdf816232 | ||
|
2d71eb8a18 | ||
|
64d2532ce9 | ||
|
0376910f33 | ||
|
6d503119a1 | ||
|
bfae93e57e | ||
|
d0e42a4798 | ||
|
2a34358abc | ||
|
fd2bb8ea45 | ||
|
98e5daa0e0 | ||
|
ad2e119282 | ||
|
c20c30d8d1 | ||
|
66d215c9c1 | ||
|
46e088d379 | ||
|
bbdd15161a | ||
|
ea9dc8cfb8 | ||
|
6bd2ccc9bf | ||
|
56327c6b58 | ||
|
712e8a51e4 | ||
|
421f324f9e | ||
|
8fe4a70299 | ||
|
3af6d0dbfd | ||
|
e2bef076d3 | ||
|
1bf9f28f4b | ||
|
f1e7b97a93 | ||
|
8cfe13ad90 | ||
|
0f420abc8e | ||
|
3b5b715567 | ||
|
520051af25 | ||
|
fd18a48608 | ||
|
64860c6287 | ||
|
58635b24ba | ||
|
3ec9dfc108 | ||
|
bd1572f11a | ||
|
95c58bd793 | ||
|
65591c7727 | ||
|
61411bb259 | ||
|
fcdb0eff8f | ||
|
30d9347272 |
2
AUTHORS
2
AUTHORS
@@ -98,3 +98,5 @@ that much better:
|
||||
* Chris Williams
|
||||
* Robert Kajic
|
||||
* Jacob Peddicord
|
||||
* Nils Hasenbanck
|
||||
* mostlystatic
|
@@ -2,6 +2,20 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
Changes in 0.6.4
|
||||
================
|
||||
|
||||
- Refactored connection / fixed replicasetconnection
|
||||
- Bug fix for unknown connection alias error message
|
||||
- Sessions support Django 1.3 and Django 1.4
|
||||
- Minor fix for ReferenceField
|
||||
|
||||
Changes in 0.6.3
|
||||
================
|
||||
- Updated sessions for Django 1.4
|
||||
- Bug fix for updates where listfields contain embedded documents
|
||||
- Bug fix for collection naming and mixins
|
||||
|
||||
Changes in 0.6.2
|
||||
================
|
||||
- Updated documentation for ReplicaSet connections
|
||||
|
@@ -2,19 +2,21 @@
|
||||
Using MongoEngine with Django
|
||||
=============================
|
||||
|
||||
.. note :: Updated to support Django 1.4
|
||||
|
||||
Connecting
|
||||
==========
|
||||
In your **settings.py** file, ignore the standard database settings (unless you
|
||||
also plan to use the ORM in your project), and instead call
|
||||
also plan to use the ORM in your project), and instead call
|
||||
:func:`~mongoengine.connect` somewhere in the settings module.
|
||||
|
||||
Authentication
|
||||
==============
|
||||
MongoEngine includes a Django authentication backend, which uses MongoDB. The
|
||||
:class:`~mongoengine.django.auth.User` model is a MongoEngine
|
||||
:class:`~mongoengine.Document`, but implements most of the methods and
|
||||
:class:`~mongoengine.django.auth.User` model is a MongoEngine
|
||||
:class:`~mongoengine.Document`, but implements most of the methods and
|
||||
attributes that the standard Django :class:`User` model does - so the two are
|
||||
moderately compatible. Using this backend will allow you to store users in
|
||||
moderately compatible. Using this backend will allow you to store users in
|
||||
MongoDB but still use many of the Django authentication infrastucture (such as
|
||||
the :func:`login_required` decorator and the :func:`authenticate` function). To
|
||||
enable the MongoEngine auth backend, add the following to you **settings.py**
|
||||
@@ -24,7 +26,7 @@ file::
|
||||
'mongoengine.django.auth.MongoEngineBackend',
|
||||
)
|
||||
|
||||
The :mod:`~mongoengine.django.auth` module also contains a
|
||||
The :mod:`~mongoengine.django.auth` module also contains a
|
||||
:func:`~mongoengine.django.auth.get_user` helper function, that takes a user's
|
||||
:attr:`id` and returns a :class:`~mongoengine.django.auth.User` object.
|
||||
|
||||
@@ -49,9 +51,9 @@ Storage
|
||||
=======
|
||||
With MongoEngine's support for GridFS via the :class:`~mongoengine.FileField`,
|
||||
it is useful to have a Django file storage backend that wraps this. The new
|
||||
storage module is called :class:`~mongoengine.django.storage.GridFSStorage`.
|
||||
storage module is called :class:`~mongoengine.django.storage.GridFSStorage`.
|
||||
Using it is very similar to using the default FileSystemStorage.::
|
||||
|
||||
|
||||
from mongoengine.django.storage import GridFSStorage
|
||||
fs = GridFSStorage()
|
||||
|
||||
|
@@ -5,15 +5,13 @@ Signals
|
||||
|
||||
.. versionadded:: 0.5
|
||||
|
||||
Signal support is provided by the excellent `blinker`_ library and
|
||||
will gracefully fall back if it is not available.
|
||||
.. note::
|
||||
|
||||
Signal support is provided by the excellent `blinker`_ library and
|
||||
will gracefully fall back if it is not available.
|
||||
|
||||
|
||||
<<<<<<< HEAD
|
||||
The following document signals exist in MongoEngine and are pretty self explanatory:
|
||||
=======
|
||||
The following document signals exist in MongoEngine and are pretty self-explanatory:
|
||||
>>>>>>> master
|
||||
|
||||
* `mongoengine.signals.pre_init`
|
||||
* `mongoengine.signals.post_init`
|
||||
|
@@ -76,7 +76,7 @@ To upgrade use a Mixin class to set meta like so ::
|
||||
class MyAceDocument(Document, BaseMixin):
|
||||
pass
|
||||
|
||||
MyAceDocument._get_collection_name() == myacedocument
|
||||
MyAceDocument._get_collection_name() == "myacedocument"
|
||||
|
||||
Alternatively, you can rename your collections eg ::
|
||||
|
||||
|
@@ -12,7 +12,7 @@ from signals import *
|
||||
__all__ = (document.__all__ + fields.__all__ + connection.__all__ +
|
||||
queryset.__all__ + signals.__all__)
|
||||
|
||||
VERSION = (0, 6, 1)
|
||||
VERSION = (0, 6, 6)
|
||||
|
||||
|
||||
def get_version():
|
||||
|
@@ -478,13 +478,18 @@ class DocumentMetaclass(type):
|
||||
attrs.update(dict([(k, v) for k, v in base.__dict__.items()
|
||||
if issubclass(v.__class__, BaseField)]))
|
||||
|
||||
# Handle simple mixin's with meta
|
||||
if hasattr(base, 'meta') and not isinstance(base, DocumentMetaclass):
|
||||
meta = attrs.get('meta', {})
|
||||
meta.update(base.meta)
|
||||
attrs['meta'] = meta
|
||||
|
||||
for p_base in base.__bases__:
|
||||
#optimize :-)
|
||||
if p_base in (object, BaseDocument):
|
||||
continue
|
||||
|
||||
attrs.update(_get_mixin_fields(p_base))
|
||||
|
||||
return attrs
|
||||
|
||||
metaclass = attrs.get('__metaclass__')
|
||||
@@ -498,6 +503,7 @@ class DocumentMetaclass(type):
|
||||
simple_class = True
|
||||
|
||||
for base in bases:
|
||||
|
||||
# Include all fields present in superclasses
|
||||
if hasattr(base, '_fields'):
|
||||
doc_fields.update(base._fields)
|
||||
@@ -526,7 +532,8 @@ class DocumentMetaclass(type):
|
||||
simple_class = False
|
||||
|
||||
doc_class_name = '.'.join(reversed(class_name))
|
||||
meta = attrs.get('_meta', attrs.get('meta', {}))
|
||||
meta = attrs.get('_meta', {})
|
||||
meta.update(attrs.get('meta', {}))
|
||||
|
||||
if 'allow_inheritance' not in meta:
|
||||
meta['allow_inheritance'] = True
|
||||
|
@@ -39,22 +39,7 @@ def register_connection(alias, name, host='localhost', port=27017,
|
||||
"""
|
||||
global _connection_settings
|
||||
|
||||
# Handle uri style connections
|
||||
if "://" in host:
|
||||
uri_dict = uri_parser.parse_uri(host)
|
||||
if uri_dict.get('database') is None:
|
||||
raise ConnectionError("If using URI style connection include "\
|
||||
"database name in string")
|
||||
_connection_settings[alias] = {
|
||||
'host': host,
|
||||
'name': uri_dict.get('database'),
|
||||
'username': uri_dict.get('username'),
|
||||
'password': uri_dict.get('password')
|
||||
}
|
||||
_connection_settings[alias].update(kwargs)
|
||||
return
|
||||
|
||||
_connection_settings[alias] = {
|
||||
conn_settings = {
|
||||
'name': name,
|
||||
'host': host,
|
||||
'port': port,
|
||||
@@ -64,7 +49,22 @@ def register_connection(alias, name, host='localhost', port=27017,
|
||||
'password': password,
|
||||
'read_preference': read_preference
|
||||
}
|
||||
_connection_settings[alias].update(kwargs)
|
||||
|
||||
# Handle uri style connections
|
||||
if "://" in host:
|
||||
uri_dict = uri_parser.parse_uri(host)
|
||||
if uri_dict.get('database') is None:
|
||||
raise ConnectionError("If using URI style connection include "\
|
||||
"database name in string")
|
||||
conn_settings.update({
|
||||
'host': host,
|
||||
'name': uri_dict.get('database'),
|
||||
'username': uri_dict.get('username'),
|
||||
'password': uri_dict.get('password'),
|
||||
'read_preference': read_preference,
|
||||
})
|
||||
|
||||
_connection_settings[alias] = conn_settings
|
||||
|
||||
|
||||
def disconnect(alias=DEFAULT_CONNECTION_NAME):
|
||||
@@ -86,7 +86,7 @@ def get_connection(alias=DEFAULT_CONNECTION_NAME, reconnect=False):
|
||||
|
||||
if alias not in _connections:
|
||||
if alias not in _connection_settings:
|
||||
msg = 'Connection with alias "%s" has not been defined'
|
||||
msg = 'Connection with alias "%s" has not been defined' % alias
|
||||
if alias == DEFAULT_CONNECTION_NAME:
|
||||
msg = 'You have not defined a default connection'
|
||||
raise ConnectionError(msg)
|
||||
@@ -105,11 +105,13 @@ def get_connection(alias=DEFAULT_CONNECTION_NAME, reconnect=False):
|
||||
for slave_alias in conn_settings['slaves']:
|
||||
slaves.append(get_connection(slave_alias))
|
||||
conn_settings['slaves'] = slaves
|
||||
conn_settings.pop('read_preference')
|
||||
conn_settings.pop('read_preference', None)
|
||||
|
||||
connection_class = Connection
|
||||
if 'replicaSet' in conn_settings:
|
||||
conn_settings['hosts_or_uri'] = conn_settings.pop('host', None)
|
||||
# Discard port since it can't be used on ReplicaSetConnection
|
||||
conn_settings.pop('port', None)
|
||||
connection_class = ReplicaSetConnection
|
||||
try:
|
||||
_connections[alias] = connection_class(**conn_settings)
|
||||
|
@@ -1,23 +1,39 @@
|
||||
import datetime
|
||||
|
||||
from mongoengine import *
|
||||
|
||||
from django.utils.hashcompat import md5_constructor, sha_constructor
|
||||
from django.utils.encoding import smart_str
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import datetime
|
||||
try:
|
||||
from django.contrib.auth.hashers import check_password, make_password
|
||||
except ImportError:
|
||||
"""Handle older versions of Django"""
|
||||
from django.utils.hashcompat import md5_constructor, sha_constructor
|
||||
|
||||
def get_hexdigest(algorithm, salt, raw_password):
|
||||
raw_password, salt = smart_str(raw_password), smart_str(salt)
|
||||
if algorithm == 'md5':
|
||||
return md5_constructor(salt + raw_password).hexdigest()
|
||||
elif algorithm == 'sha1':
|
||||
return sha_constructor(salt + raw_password).hexdigest()
|
||||
raise ValueError('Got unknown password algorithm type in password')
|
||||
|
||||
def check_password(raw_password, password):
|
||||
algo, salt, hash = password.split('$')
|
||||
return hash == get_hexdigest(algo, salt, raw_password)
|
||||
|
||||
def make_password(raw_password):
|
||||
from random import random
|
||||
algo = 'sha1'
|
||||
salt = get_hexdigest(algo, str(random()), str(random()))[:5]
|
||||
hash = get_hexdigest(algo, salt, raw_password)
|
||||
return '%s$%s$%s' % (algo, salt, hash)
|
||||
|
||||
|
||||
REDIRECT_FIELD_NAME = 'next'
|
||||
|
||||
def get_hexdigest(algorithm, salt, raw_password):
|
||||
raw_password, salt = smart_str(raw_password), smart_str(salt)
|
||||
if algorithm == 'md5':
|
||||
return md5_constructor(salt + raw_password).hexdigest()
|
||||
elif algorithm == 'sha1':
|
||||
return sha_constructor(salt + raw_password).hexdigest()
|
||||
raise ValueError('Got unknown password algorithm type in password')
|
||||
|
||||
|
||||
class User(Document):
|
||||
"""A User document that aims to mirror most of the API specified by Django
|
||||
at http://docs.djangoproject.com/en/dev/topics/auth/#users
|
||||
@@ -34,7 +50,7 @@ class User(Document):
|
||||
email = EmailField(verbose_name=_('e-mail address'))
|
||||
password = StringField(max_length=128,
|
||||
verbose_name=_('password'),
|
||||
help_text=_("Use '[algo]$[salt]$[hexdigest]' or use the <a href=\"password/\">change password form</a>."))
|
||||
help_text=_("Use '[algo]$[iterations]$[salt]$[hexdigest]' or use the <a href=\"password/\">change password form</a>."))
|
||||
is_staff = BooleanField(default=False,
|
||||
verbose_name=_('staff status'),
|
||||
help_text=_("Designates whether the user can log into this admin site."))
|
||||
@@ -75,11 +91,7 @@ class User(Document):
|
||||
assigning to :attr:`~mongoengine.django.auth.User.password` as the
|
||||
password is hashed before storage.
|
||||
"""
|
||||
from random import random
|
||||
algo = 'sha1'
|
||||
salt = get_hexdigest(algo, str(random()), str(random()))[:5]
|
||||
hash = get_hexdigest(algo, salt, raw_password)
|
||||
self.password = '%s$%s$%s' % (algo, salt, hash)
|
||||
self.password = make_password(raw_password)
|
||||
self.save()
|
||||
return self
|
||||
|
||||
@@ -89,8 +101,7 @@ class User(Document):
|
||||
:attr:`~mongoengine.django.auth.User.password` as the password is
|
||||
hashed before storage.
|
||||
"""
|
||||
algo, salt, hash = self.password.split('$')
|
||||
return hash == get_hexdigest(algo, salt, raw_password)
|
||||
return check_password(raw_password, self.password)
|
||||
|
||||
@classmethod
|
||||
def create_user(cls, username, password, email=None):
|
||||
|
@@ -1,3 +1,6 @@
|
||||
from datetime import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.sessions.backends.base import SessionBase, CreateError
|
||||
from django.core.exceptions import SuspiciousOperation
|
||||
from django.utils.encoding import force_unicode
|
||||
@@ -6,18 +9,18 @@ from mongoengine.document import Document
|
||||
from mongoengine import fields
|
||||
from mongoengine.queryset import OperationError
|
||||
from mongoengine.connection import DEFAULT_CONNECTION_NAME
|
||||
from django.conf import settings
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
MONGOENGINE_SESSION_DB_ALIAS = getattr(
|
||||
settings, 'MONGOENGINE_SESSION_DB_ALIAS',
|
||||
DEFAULT_CONNECTION_NAME)
|
||||
|
||||
|
||||
class MongoSession(Document):
|
||||
session_key = fields.StringField(primary_key=True, max_length=40)
|
||||
session_data = fields.StringField()
|
||||
expire_date = fields.DateTimeField()
|
||||
|
||||
|
||||
meta = {'collection': 'django_session',
|
||||
'db_alias': MONGOENGINE_SESSION_DB_ALIAS,
|
||||
'allow_inheritance': False}
|
||||
@@ -41,7 +44,7 @@ class SessionStore(SessionBase):
|
||||
|
||||
def create(self):
|
||||
while True:
|
||||
self.session_key = self._get_new_session_key()
|
||||
self._session_key = self._get_new_session_key()
|
||||
try:
|
||||
self.save(must_create=True)
|
||||
except CreateError:
|
||||
@@ -51,6 +54,8 @@ class SessionStore(SessionBase):
|
||||
return
|
||||
|
||||
def save(self, must_create=False):
|
||||
if self.session_key is None:
|
||||
self.create()
|
||||
s = MongoSession(session_key=self.session_key)
|
||||
s.session_data = self.encode(self._get_session(no_load=must_create))
|
||||
s.expire_date = self.get_expiry_date()
|
||||
|
@@ -8,7 +8,7 @@ import uuid
|
||||
from bson import Binary, DBRef, SON, ObjectId
|
||||
|
||||
from base import (BaseField, ComplexBaseField, ObjectIdField,
|
||||
ValidationError, get_document)
|
||||
ValidationError, get_document, BaseDocument)
|
||||
from queryset import DO_NOTHING, QuerySet
|
||||
from document import Document, EmbeddedDocument
|
||||
from connection import get_db, DEFAULT_CONNECTION_NAME
|
||||
@@ -497,6 +497,7 @@ class ListField(ComplexBaseField):
|
||||
def prepare_query_value(self, op, value):
|
||||
if self.field:
|
||||
if op in ('set', 'unset') and (not isinstance(value, basestring)
|
||||
and not isinstance(value, BaseDocument)
|
||||
and hasattr(value, '__iter__')):
|
||||
return [self.field.prepare_query_value(op, v) for v in value]
|
||||
return self.field.prepare_query_value(op, value)
|
||||
@@ -656,6 +657,9 @@ class ReferenceField(BaseField):
|
||||
return super(ReferenceField, self).__get__(instance, owner)
|
||||
|
||||
def to_mongo(self, document):
|
||||
if isinstance(document, DBRef):
|
||||
return document
|
||||
|
||||
id_field_name = self.document_type._meta['id_field']
|
||||
id_field = self.document_type._fields[id_field_name]
|
||||
|
||||
|
@@ -5,7 +5,7 @@
|
||||
%define srcname mongoengine
|
||||
|
||||
Name: python-%{srcname}
|
||||
Version: 0.6.2
|
||||
Version: 0.6.6
|
||||
Release: 1%{?dist}
|
||||
Summary: A Python Document-Object Mapper for working with MongoDB
|
||||
|
||||
@@ -51,6 +51,18 @@ rm -rf $RPM_BUILD_ROOT
|
||||
# %{python_sitearch}/*
|
||||
|
||||
%changelog
|
||||
* Wed Apr 24 2012 Ross Lawley <ross.lawley@gmail.com> 0.6.5
|
||||
- 0.6.6 released
|
||||
* Wed Apr 18 2012 Ross Lawley <ross.lawley@gmail.com> 0.6.5
|
||||
- 0.6.5 released
|
||||
* Wed Apr 18 2012 Ross Lawley <ross.lawley@gmail.com> 0.6.5
|
||||
- 0.6.4 released
|
||||
* Wed Mar 24 2012 Ross Lawley <ross.lawley@gmail.com> 0.6.5
|
||||
- 0.6.3 released
|
||||
* Wed Mar 22 2012 Ross Lawley <ross.lawley@gmail.com> 0.6.5
|
||||
- 0.6.2 released
|
||||
* Wed Mar 05 2012 Ross Lawley <ross.lawley@gmail.com> 0.6.5
|
||||
- 0.6.1 released
|
||||
* Mon Mar 05 2012 Ross Lawley <ross.lawley@gmail.com> 0.6
|
||||
- 0.6 released
|
||||
* Thu Oct 27 2011 Pau Aliagas <linuxnow@gmail.com> 0.5.3-1
|
||||
|
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
pymongo
|
@@ -12,6 +12,10 @@ from django.core.paginator import Paginator
|
||||
|
||||
settings.configure()
|
||||
|
||||
from django.contrib.sessions.tests import SessionTestsMixin
|
||||
from mongoengine.django.sessions import SessionStore, MongoSession
|
||||
|
||||
|
||||
class QuerySetTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
@@ -88,3 +92,14 @@ class QuerySetTest(unittest.TestCase):
|
||||
end = p * 2
|
||||
start = end - 1
|
||||
self.assertEqual(t.render(Context(d)), u'%d:%d:' % (start, end))
|
||||
|
||||
|
||||
|
||||
class MongoDBSessionTest(SessionTestsMixin, unittest.TestCase):
|
||||
backend = SessionStore
|
||||
|
||||
def setUp(self):
|
||||
connect(db='mongoenginetest')
|
||||
MongoSession.drop_collection()
|
||||
super(MongoDBSessionTest, self).setUp()
|
||||
|
||||
|
@@ -96,7 +96,7 @@ class DocumentTest(unittest.TestCase):
|
||||
# Ensure Document isn't treated like an actual document
|
||||
self.assertFalse(hasattr(Document, '_fields'))
|
||||
|
||||
def test_collection_name(self):
|
||||
def test_collection_naming(self):
|
||||
"""Ensure that a collection with a specified name may be used.
|
||||
"""
|
||||
|
||||
@@ -157,11 +157,12 @@ class DocumentTest(unittest.TestCase):
|
||||
}
|
||||
|
||||
class BaseDocument(Document, BaseMixin):
|
||||
pass
|
||||
meta = {'allow_inheritance': True}
|
||||
|
||||
class MyDocument(BaseDocument):
|
||||
pass
|
||||
self.assertEquals('mydocument', OldMixinNamingConvention._get_collection_name())
|
||||
|
||||
self.assertEquals('basedocument', MyDocument._get_collection_name())
|
||||
|
||||
def test_get_superclasses(self):
|
||||
"""Ensure that the correct list of superclasses is assembled.
|
||||
|
@@ -1518,6 +1518,37 @@ class QuerySetTest(unittest.TestCase):
|
||||
|
||||
BlogPost.drop_collection()
|
||||
|
||||
|
||||
def test_set_list_embedded_documents(self):
|
||||
|
||||
class Author(EmbeddedDocument):
|
||||
name = StringField()
|
||||
|
||||
class Message(Document):
|
||||
title = StringField()
|
||||
authors = ListField(EmbeddedDocumentField('Author'))
|
||||
|
||||
Message.drop_collection()
|
||||
|
||||
message = Message(title="hello", authors=[Author(name="Harry")])
|
||||
message.save()
|
||||
|
||||
Message.objects(authors__name="Harry").update_one(
|
||||
set__authors__S=Author(name="Ross"))
|
||||
|
||||
message = message.reload()
|
||||
self.assertEquals(message.authors[0].name, "Ross")
|
||||
|
||||
Message.objects(authors__name="Ross").update_one(
|
||||
set__authors=[Author(name="Harry"),
|
||||
Author(name="Ross"),
|
||||
Author(name="Adam")])
|
||||
|
||||
message = message.reload()
|
||||
self.assertEquals(message.authors[0].name, "Harry")
|
||||
self.assertEquals(message.authors[1].name, "Ross")
|
||||
self.assertEquals(message.authors[2].name, "Adam")
|
||||
|
||||
def test_order_by(self):
|
||||
"""Ensure that QuerySets may be ordered.
|
||||
"""
|
||||
|
Reference in New Issue
Block a user