Compare commits

...

7 Commits

Author SHA1 Message Date
Harry Marr
ef5815e4a5 Bump to v0.1.3 2010-01-07 22:49:24 +00:00
Harry Marr
b7e8108edd Added docs about using MongoEngine with Django 2010-01-07 22:48:39 +00:00
Harry Marr
d48296eacc Added create_user method to Django User model 2010-01-07 22:25:26 +00:00
Harry Marr
e0a546000d Added Django authentication backend 2010-01-07 18:56:28 +00:00
Harry Marr
4c93e2945c Added test for meta[indexes] 2010-01-07 15:46:52 +00:00
blackbrrr
a6d64b2010 added meta support for indexes ensured at call-time 2010-01-07 23:28:10 +08:00
Harry Marr
2e74c93878 Minor bugfixes 2010-01-07 15:24:52 +00:00
9 changed files with 159 additions and 11 deletions

View File

@@ -2,6 +2,14 @@
Changelog Changelog
========= =========
Changes is v0.1.3
=================
- Added Django authentication backend
- Added Document.meta support for indexes, which are ensured just before
querying takes place
- A few minor bugfixes
Changes in v0.1.2 Changes in v0.1.2
================= =================
- Query values may be processed before before being used in queries - Query values may be processed before before being used in queries

View File

@@ -16,6 +16,7 @@ The source is available on `GitHub <http://github.com/hmarr/mongoengine>`_.
tutorial tutorial
userguide userguide
apireference apireference
django
changelog changelog
Indices and tables Indices and tables

View File

@@ -12,7 +12,7 @@ __all__ = (document.__all__ + fields.__all__ + connection.__all__ +
__author__ = 'Harry Marr' __author__ = 'Harry Marr'
VERSION = (0, 1, 2) VERSION = (0, 1, 3)
def get_version(): def get_version():
version = '%s.%s' % (VERSION[0], VERSION[1]) version = '%s.%s' % (VERSION[0], VERSION[1])

View File

@@ -154,8 +154,12 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
'allow_inheritance': True, 'allow_inheritance': True,
'max_documents': None, 'max_documents': None,
'max_size': None, 'max_size': None,
'indexes': [] # indexes to be ensured at runtime
} }
# Apply document-defined meta options
meta.update(attrs.get('meta', {})) meta.update(attrs.get('meta', {}))
# Only simple classes - direct subclasses of Document - may set # Only simple classes - direct subclasses of Document - may set
# allow_inheritance to False # allow_inheritance to False
if not simple_class and not meta['allow_inheritance']: if not simple_class and not meta['allow_inheritance']:
@@ -202,9 +206,7 @@ class BaseDocument(object):
return all_subclasses return all_subclasses
def __iter__(self): def __iter__(self):
# Use _data rather than _fields as iterator only looks at names so return iter(self._fields)
# values don't need to be converted to Python types
return iter(self._data)
def __getitem__(self, name): def __getitem__(self, name):
"""Dictionary-style field access, return a field's value if present. """Dictionary-style field access, return a field's value if present.

View File

View File

@@ -0,0 +1,99 @@
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
import datetime
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
"""
username = StringField(max_length=30, required=True)
first_name = StringField(max_length=30)
last_name = StringField(max_length=30)
email = StringField()
password = StringField(max_length=128)
is_staff = BooleanField(default=False)
is_active = BooleanField(default=True)
is_superuser = BooleanField(default=False)
last_login = DateTimeField(default=datetime.datetime.now)
def get_full_name(self):
"""Returns the users first and last names, separated by a space.
"""
full_name = u'%s %s' % (self.first_name or '', self.last_name or '')
return full_name.strip()
def is_anonymous(self):
return False
def is_authenticated(self):
return True
def set_password(self, raw_password):
"""Sets the user's password - always use this rather than directly
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)
def check_password(self, raw_password):
"""Checks the user's password against a provided password - always use
this rather than directly comparing to
: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)
@classmethod
def create_user(cls, username, password, email=None):
"""Create (and save) a new user with the given username, password and
email address.
"""
user = User(username=username, email=email)
user.set_password(password)
user.save()
return user
class MongoEngineBackend(object):
"""Authenticate using MongoEngine and mongoengine.django.auth.User.
"""
def authenticate(self, username=None, password=None):
user = User.objects(username=username).first()
if user:
if password and user.check_password(password):
return user
return None
def get_user(self, user_id):
return User.objects.with_id(user_id)
def get_user(userid):
"""Returns a User object from an id (User.id). Django's equivalent takes
request, but taking an id instead leaves it up to the developer to store
the id in any way they want (session, signed cookie, etc.)
"""
if not userid:
return AnonymousUser()
return MongoEngineBackend().get_user(userid) or AnonymousUser()

View File

@@ -67,8 +67,6 @@ class Document(BaseDocument):
def reload(self): def reload(self):
"""Reloads all attributes from the database. """Reloads all attributes from the database.
""" """
#object_id = self._fields['id'].to_mongo(self.id)
#obj = self.__class__.objects(id=object_id).first()
obj = self.__class__.objects(id=self.id).first() obj = self.__class__.objects(id=self.id).first()
for field in self._fields: for field in self._fields:
setattr(self, field, getattr(obj, field)) setattr(self, field, getattr(obj, field))

View File

@@ -25,23 +25,37 @@ class QuerySet(object):
self._query = {'_types': self._document._class_name} self._query = {'_types': self._document._class_name}
self._cursor_obj = None self._cursor_obj = None
def ensure_index(self, key_or_list, direction=None): def ensure_index(self, key_or_list):
"""Ensure that the given indexes are in place. """Ensure that the given indexes are in place.
""" """
if isinstance(key_or_list, basestring): if isinstance(key_or_list, basestring):
# single-field indexes needn't specify a direction # single-field indexes needn't specify a direction
if key_or_list.startswith("-"): if key_or_list.startswith("-") or key_or_list.startswith("+"):
key_or_list = key_or_list[1:] key_or_list = key_or_list[1:]
self._collection.ensure_index(key_or_list) self._collection.ensure_index(key_or_list)
elif isinstance(key_or_list, (list, tuple)): elif isinstance(key_or_list, (list, tuple)):
print key_or_list index_list = []
self._collection.ensure_index(key_or_list) for key in key_or_list:
if key.startswith("-"):
index_list.append((key[1:], pymongo.DESCENDING))
else:
if key.startswith("+"):
key = key[1:]
index_list.append((key, pymongo.ASCENDING))
self._collection.ensure_index(index_list)
return self return self
def __call__(self, **query): def __call__(self, **query):
"""Filter the selected documents by calling the """Filter the selected documents by calling the
:class:`~mongoengine.QuerySet` with a query. :class:`~mongoengine.QuerySet` with a query.
""" """
# ensure document-defined indexes are created
if self._document._meta['indexes']:
for key_or_list in self._document._meta['indexes']:
# print "key", key_or_list
self.ensure_index(key_or_list)
query = QuerySet._transform_query(_doc_cls=self._document, **query) query = QuerySet._transform_query(_doc_cls=self._document, **query)
self._query.update(query) self._query.update(query)
return self return self
@@ -132,7 +146,7 @@ class QuerySet(object):
"""Retrieve the object matching the id provided. """Retrieve the object matching the id provided.
""" """
if not isinstance(object_id, pymongo.objectid.ObjectId): if not isinstance(object_id, pymongo.objectid.ObjectId):
object_id = pymongo.objectid.ObjectId(object_id) object_id = pymongo.objectid.ObjectId(str(object_id))
result = self._collection.find_one(object_id) result = self._collection.find_one(object_id)
if result is not None: if result is not None:

View File

@@ -221,6 +221,32 @@ class DocumentTest(unittest.TestCase):
Log.drop_collection() Log.drop_collection()
def test_indexes(self):
"""Ensure that indexes are used when meta[indexes] is specified.
"""
class BlogPost(Document):
date = DateTimeField(default=datetime.datetime.now)
category = StringField()
meta = {
'indexes': [
'-date',
('category', '-date')
],
}
BlogPost.drop_collection()
info = BlogPost.objects._collection.index_information()
self.assertEqual(len(info), 0)
BlogPost.objects()
info = BlogPost.objects._collection.index_information()
self.assertTrue([('category', 1), ('date', -1)] in info.values())
# Even though descending order was specified, single-key indexes use 1
self.assertTrue([('date', 1)] in info.values())
BlogPost.drop_collection()
def test_creation(self): def test_creation(self):
"""Ensure that document may be created using keyword arguments. """Ensure that document may be created using keyword arguments.
""" """