Removed Django Support from MongoEngine
Django support has now been split out of MongoEngine and will be revisted as a new but separate module. Closes #958
This commit is contained in:
parent
7e1a5ce445
commit
7530f03bf6
21
.travis.yml
21
.travis.yml
@ -8,24 +8,18 @@ python:
|
||||
- pypy
|
||||
- pypy3
|
||||
env:
|
||||
- PYMONGO=2.7.2 DJANGO=dev
|
||||
- PYMONGO=2.7.2 DJANGO=1.7.1
|
||||
- PYMONGO=2.7.2 DJANGO=1.6.8
|
||||
- PYMONGO=2.7.2 DJANGO=1.5.11
|
||||
- PYMONGO=2.8 DJANGO=dev
|
||||
- PYMONGO=2.8 DJANGO=1.7.1
|
||||
- PYMONGO=2.8 DJANGO=1.6.8
|
||||
- PYMONGO=2.8 DJANGO=1.5.11
|
||||
- PYMONGO=2.7.2
|
||||
- PYMONGO=2.8
|
||||
matrix:
|
||||
exclude:
|
||||
- python: '2.6'
|
||||
env: PYMONGO=2.7.2 DJANGO=dev
|
||||
env: PYMONGO=2.7.2
|
||||
- python: '2.6'
|
||||
env: PYMONGO=2.8 DJANGO=dev
|
||||
env: PYMONGO=2.8
|
||||
- python: '2.6'
|
||||
env: PYMONGO=2.7.2 DJANGO=1.7.1
|
||||
env: PYMONGO=2.7.2
|
||||
- python: '2.6'
|
||||
env: PYMONGO=2.8 DJANGO=1.7.1
|
||||
env: PYMONGO=2.8
|
||||
allow_failures:
|
||||
- python: pypy3
|
||||
fast_finish: true
|
||||
@ -43,9 +37,6 @@ install:
|
||||
true; fi
|
||||
- if [[ $PYMONGO != 'dev' ]]; then travis_retry pip install pymongo==$PYMONGO; true;
|
||||
fi
|
||||
- if [[ $DJANGO == 'dev' ]]; then travis_retry pip install git+https://github.com/django/django.git;
|
||||
fi
|
||||
- if [[ $DJANGO != 'dev' ]]; then travis_retry pip install Django==$DJANGO; fi
|
||||
- travis_retry pip install https://pypi.python.org/packages/source/p/python-dateutil/python-dateutil-2.1.tar.gz#md5=1534bb15cf311f07afaa3aacba1c028b
|
||||
- travis_retry pip install coveralls
|
||||
- travis_retry python setup.py install
|
||||
|
@ -11,6 +11,7 @@ Changes in 0.9.X - DEV
|
||||
- Use sets for populating dbrefs to dereference
|
||||
- Fixed unpickled documents replacing the global field's list. #888
|
||||
- Fixed storage of microseconds in ComplexDateTimeField and unused separator option. #910
|
||||
- Django support was removed and will be available as a separate extension. #958
|
||||
|
||||
Changes in 0.9.0
|
||||
================
|
||||
|
173
docs/django.rst
173
docs/django.rst
@ -2,176 +2,5 @@
|
||||
Django Support
|
||||
==============
|
||||
|
||||
.. note:: Updated to support Django 1.5
|
||||
|
||||
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
|
||||
:func:`~mongoengine.connect` somewhere in the settings module.
|
||||
|
||||
.. note::
|
||||
If you are not using another Database backend you may need to add a dummy
|
||||
database backend to ``settings.py`` eg::
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.dummy'
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
MongoDB but still use many of the Django authentication infrastructure (such as
|
||||
the :func:`login_required` decorator and the :func:`authenticate` function). To
|
||||
enable the MongoEngine auth backend, add the following to your **settings.py**
|
||||
file::
|
||||
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
'mongoengine.django.auth.MongoEngineBackend',
|
||||
)
|
||||
|
||||
The :mod:`~mongoengine.django.auth` module also contains a
|
||||
:func:`~mongoengine.django.auth.get_user` helper function, that takes a user's
|
||||
:attr:`id` and returns a :class:`~mongoengine.django.auth.User` object.
|
||||
|
||||
.. versionadded:: 0.1.3
|
||||
|
||||
Custom User model
|
||||
=================
|
||||
Django 1.5 introduced `Custom user Models
|
||||
<https://docs.djangoproject.com/en/dev/topics/auth/customizing/#auth-custom-user>`_
|
||||
which can be used as an alternative to the MongoEngine authentication backend.
|
||||
|
||||
The main advantage of this option is that other components relying on
|
||||
:mod:`django.contrib.auth` and supporting the new swappable user model are more
|
||||
likely to work. For example, you can use the ``createsuperuser`` management
|
||||
command as usual.
|
||||
|
||||
To enable the custom User model in Django, add ``'mongoengine.django.mongo_auth'``
|
||||
in your ``INSTALLED_APPS`` and set ``'mongo_auth.MongoUser'`` as the custom user
|
||||
user model to use. In your **settings.py** file you will have::
|
||||
|
||||
INSTALLED_APPS = (
|
||||
...
|
||||
'django.contrib.auth',
|
||||
'mongoengine.django.mongo_auth',
|
||||
...
|
||||
)
|
||||
|
||||
AUTH_USER_MODEL = 'mongo_auth.MongoUser'
|
||||
|
||||
An additional ``MONGOENGINE_USER_DOCUMENT`` setting enables you to replace the
|
||||
:class:`~mongoengine.django.auth.User` class with another class of your choice::
|
||||
|
||||
MONGOENGINE_USER_DOCUMENT = 'mongoengine.django.auth.User'
|
||||
|
||||
The custom :class:`User` must be a :class:`~mongoengine.Document` class, but
|
||||
otherwise has the same requirements as a standard custom user model,
|
||||
as specified in the `Django Documentation
|
||||
<https://docs.djangoproject.com/en/dev/topics/auth/customizing/>`_.
|
||||
In particular, the custom class must define :attr:`USERNAME_FIELD` and
|
||||
:attr:`REQUIRED_FIELDS` attributes.
|
||||
|
||||
Sessions
|
||||
========
|
||||
Django allows the use of different backend stores for its sessions. MongoEngine
|
||||
provides a MongoDB-based session backend for Django, which allows you to use
|
||||
sessions in your Django application with just MongoDB. To enable the MongoEngine
|
||||
session backend, ensure that your settings module has
|
||||
``'django.contrib.sessions.middleware.SessionMiddleware'`` in the
|
||||
``MIDDLEWARE_CLASSES`` field and ``'django.contrib.sessions'`` in your
|
||||
``INSTALLED_APPS``. From there, all you need to do is add the following line
|
||||
into your settings module::
|
||||
|
||||
SESSION_ENGINE = 'mongoengine.django.sessions'
|
||||
SESSION_SERIALIZER = 'mongoengine.django.sessions.BSONSerializer'
|
||||
|
||||
Django provides session cookie, which expires after ```SESSION_COOKIE_AGE``` seconds, but doesn't delete cookie at sessions backend, so ``'mongoengine.django.sessions'`` supports `mongodb TTL
|
||||
<http://docs.mongodb.org/manual/tutorial/expire-data/>`_.
|
||||
|
||||
.. note:: ``SESSION_SERIALIZER`` is only necessary in Django 1.6 as the default
|
||||
serializer is based around JSON and doesn't know how to convert
|
||||
``bson.objectid.ObjectId`` instances to strings.
|
||||
|
||||
.. versionadded:: 0.2.1
|
||||
|
||||
Storage
|
||||
=======
|
||||
With MongoEngine's support for GridFS via the :class:`~mongoengine.fields.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`.
|
||||
Using it is very similar to using the default FileSystemStorage.::
|
||||
|
||||
from mongoengine.django.storage import GridFSStorage
|
||||
fs = GridFSStorage()
|
||||
|
||||
filename = fs.save('hello.txt', 'Hello, World!')
|
||||
|
||||
All of the `Django Storage API methods
|
||||
<http://docs.djangoproject.com/en/dev/ref/files/storage/>`_ have been
|
||||
implemented except :func:`path`. If the filename provided already exists, an
|
||||
underscore and a number (before # the file extension, if one exists) will be
|
||||
appended to the filename until the generated filename doesn't exist. The
|
||||
:func:`save` method will return the new filename.::
|
||||
|
||||
>>> fs.exists('hello.txt')
|
||||
True
|
||||
>>> fs.open('hello.txt').read()
|
||||
'Hello, World!'
|
||||
>>> fs.size('hello.txt')
|
||||
13
|
||||
>>> fs.url('hello.txt')
|
||||
'http://your_media_url/hello.txt'
|
||||
>>> fs.open('hello.txt').name
|
||||
'hello.txt'
|
||||
>>> fs.listdir()
|
||||
([], [u'hello.txt'])
|
||||
|
||||
All files will be saved and retrieved in GridFS via the :class:`FileDocument`
|
||||
document, allowing easy access to the files without the GridFSStorage
|
||||
backend.::
|
||||
|
||||
>>> from mongoengine.django.storage import FileDocument
|
||||
>>> FileDocument.objects()
|
||||
[<FileDocument: FileDocument object>]
|
||||
|
||||
.. versionadded:: 0.4
|
||||
|
||||
Shortcuts
|
||||
=========
|
||||
Inspired by the `Django shortcut get_object_or_404
|
||||
<https://docs.djangoproject.com/en/dev/topics/http/shortcuts/#get-object-or-404>`_,
|
||||
the :func:`~mongoengine.django.shortcuts.get_document_or_404` method returns
|
||||
a document or raises an Http404 exception if the document does not exist::
|
||||
|
||||
from mongoengine.django.shortcuts import get_document_or_404
|
||||
|
||||
admin_user = get_document_or_404(User, username='root')
|
||||
|
||||
The first argument may be a Document or QuerySet object. All other passed arguments
|
||||
and keyword arguments are used in the query::
|
||||
|
||||
foo_email = get_document_or_404(User.objects.only('email'), username='foo', is_active=True).email
|
||||
|
||||
.. note:: Like with :func:`get`, a MultipleObjectsReturned will be raised if more than one
|
||||
object is found.
|
||||
|
||||
|
||||
Also inspired by the `Django shortcut get_list_or_404
|
||||
<https://docs.djangoproject.com/en/dev/topics/http/shortcuts/#get-list-or-404>`_,
|
||||
the :func:`~mongoengine.django.shortcuts.get_list_or_404` method returns a list of
|
||||
documents or raises an Http404 exception if the list is empty::
|
||||
|
||||
from mongoengine.django.shortcuts import get_list_or_404
|
||||
|
||||
active_users = get_list_or_404(User, is_active=True)
|
||||
|
||||
The first argument may be a Document or QuerySet object. All other passed
|
||||
arguments and keyword arguments are used to filter the query.
|
||||
.. note:: Django support has been split from MongoEngine and will be available on Github as a separate module. `See Here <https://github.com/MongoEngine/django-mongoengine>`_.
|
||||
|
||||
|
@ -10,7 +10,6 @@ import signals
|
||||
from signals import *
|
||||
from errors import *
|
||||
import errors
|
||||
import django
|
||||
|
||||
__all__ = (list(document.__all__) + fields.__all__ + connection.__all__ +
|
||||
list(queryset.__all__) + signals.__all__ + list(errors.__all__))
|
||||
|
@ -1,412 +0,0 @@
|
||||
from mongoengine import *
|
||||
|
||||
from django.utils.encoding import smart_str
|
||||
from django.contrib.auth.models import _user_has_perm, _user_get_all_permissions, _user_has_module_perms
|
||||
from django.db import models
|
||||
from django.contrib.contenttypes.models import ContentTypeManager
|
||||
from django.contrib import auth
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from .utils import datetime_now
|
||||
|
||||
REDIRECT_FIELD_NAME = 'next'
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class ContentType(Document):
|
||||
name = StringField(max_length=100)
|
||||
app_label = StringField(max_length=100)
|
||||
model = StringField(max_length=100, verbose_name=_('python model class name'),
|
||||
unique_with='app_label')
|
||||
objects = ContentTypeManager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('content type')
|
||||
verbose_name_plural = _('content types')
|
||||
# db_table = 'django_content_type'
|
||||
# ordering = ('name',)
|
||||
# unique_together = (('app_label', 'model'),)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
def model_class(self):
|
||||
"Returns the Python model class for this type of content."
|
||||
from django.db import models
|
||||
return models.get_model(self.app_label, self.model)
|
||||
|
||||
def get_object_for_this_type(self, **kwargs):
|
||||
"""
|
||||
Returns an object of this type for the keyword arguments given.
|
||||
Basically, this is a proxy around this object_type's get_object() model
|
||||
method. The ObjectNotExist exception, if thrown, will not be caught,
|
||||
so code that calls this method should catch it.
|
||||
"""
|
||||
return self.model_class()._default_manager.using(self._state.db).get(**kwargs)
|
||||
|
||||
def natural_key(self):
|
||||
return (self.app_label, self.model)
|
||||
|
||||
|
||||
class SiteProfileNotAvailable(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class PermissionManager(models.Manager):
|
||||
def get_by_natural_key(self, codename, app_label, model):
|
||||
return self.get(
|
||||
codename=codename,
|
||||
content_type=ContentType.objects.get_by_natural_key(app_label, model)
|
||||
)
|
||||
|
||||
|
||||
class Permission(Document):
|
||||
"""The permissions system provides a way to assign permissions to specific
|
||||
users and groups of users.
|
||||
|
||||
The permission system is used by the Django admin site, but may also be
|
||||
useful in your own code. The Django admin site uses permissions as follows:
|
||||
|
||||
- The "add" permission limits the user's ability to view the "add"
|
||||
form and add an object.
|
||||
- The "change" permission limits a user's ability to view the change
|
||||
list, view the "change" form and change an object.
|
||||
- The "delete" permission limits the ability to delete an object.
|
||||
|
||||
Permissions are set globally per type of object, not per specific object
|
||||
instance. It is possible to say "Mary may change news stories," but it's
|
||||
not currently possible to say "Mary may change news stories, but only the
|
||||
ones she created herself" or "Mary may only change news stories that have
|
||||
a certain status or publication date."
|
||||
|
||||
Three basic permissions -- add, change and delete -- are automatically
|
||||
created for each Django model.
|
||||
"""
|
||||
name = StringField(max_length=50, verbose_name=_('username'))
|
||||
content_type = ReferenceField(ContentType)
|
||||
codename = StringField(max_length=100, verbose_name=_('codename'))
|
||||
# FIXME: don't access field of the other class
|
||||
# unique_with=['content_type__app_label', 'content_type__model'])
|
||||
|
||||
objects = PermissionManager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('permission')
|
||||
verbose_name_plural = _('permissions')
|
||||
# unique_together = (('content_type', 'codename'),)
|
||||
# ordering = ('content_type__app_label', 'content_type__model', 'codename')
|
||||
|
||||
def __unicode__(self):
|
||||
return u"%s | %s | %s" % (
|
||||
unicode(self.content_type.app_label),
|
||||
unicode(self.content_type),
|
||||
unicode(self.name))
|
||||
|
||||
def natural_key(self):
|
||||
return (self.codename,) + self.content_type.natural_key()
|
||||
natural_key.dependencies = ['contenttypes.contenttype']
|
||||
|
||||
|
||||
class Group(Document):
|
||||
"""Groups are a generic way of categorizing users to apply permissions,
|
||||
or some other label, to those users. A user can belong to any number of
|
||||
groups.
|
||||
|
||||
A user in a group automatically has all the permissions granted to that
|
||||
group. For example, if the group Site editors has the permission
|
||||
can_edit_home_page, any user in that group will have that permission.
|
||||
|
||||
Beyond permissions, groups are a convenient way to categorize users to
|
||||
apply some label, or extended functionality, to them. For example, you
|
||||
could create a group 'Special users', and you could write code that would
|
||||
do special things to those users -- such as giving them access to a
|
||||
members-only portion of your site, or sending them members-only
|
||||
e-mail messages.
|
||||
"""
|
||||
name = StringField(max_length=80, unique=True, verbose_name=_('name'))
|
||||
permissions = ListField(ReferenceField(Permission, verbose_name=_('permissions'), required=False))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('group')
|
||||
verbose_name_plural = _('groups')
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class UserManager(models.Manager):
|
||||
def create_user(self, username, email, password=None):
|
||||
"""
|
||||
Creates and saves a User with the given username, e-mail and password.
|
||||
"""
|
||||
now = datetime_now()
|
||||
|
||||
# Normalize the address by lowercasing the domain part of the email
|
||||
# address.
|
||||
try:
|
||||
email_name, domain_part = email.strip().split('@', 1)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
email = '@'.join([email_name, domain_part.lower()])
|
||||
|
||||
user = self.model(username=username, email=email, is_staff=False,
|
||||
is_active=True, is_superuser=False, last_login=now,
|
||||
date_joined=now)
|
||||
|
||||
user.set_password(password)
|
||||
user.save(using=self._db)
|
||||
return user
|
||||
|
||||
def create_superuser(self, username, email, password):
|
||||
u = self.create_user(username, email, password)
|
||||
u.is_staff = True
|
||||
u.is_active = True
|
||||
u.is_superuser = True
|
||||
u.save(using=self._db)
|
||||
return u
|
||||
|
||||
def make_random_password(self, length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'):
|
||||
"Generates a random password with the given length and given allowed_chars"
|
||||
# Note that default value of allowed_chars does not have "I" or letters
|
||||
# that look like it -- just to avoid confusion.
|
||||
from random import choice
|
||||
return ''.join([choice(allowed_chars) for i in range(length)])
|
||||
|
||||
|
||||
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,
|
||||
verbose_name=_('username'),
|
||||
help_text=_("Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters"))
|
||||
|
||||
first_name = StringField(max_length=30,
|
||||
verbose_name=_('first name'))
|
||||
|
||||
last_name = StringField(max_length=30,
|
||||
verbose_name=_('last name'))
|
||||
email = EmailField(verbose_name=_('e-mail address'))
|
||||
password = StringField(max_length=128,
|
||||
verbose_name=_('password'),
|
||||
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."))
|
||||
is_active = BooleanField(default=True,
|
||||
verbose_name=_('active'),
|
||||
help_text=_("Designates whether this user should be treated as active. Unselect this instead of deleting accounts."))
|
||||
is_superuser = BooleanField(default=False,
|
||||
verbose_name=_('superuser status'),
|
||||
help_text=_("Designates that this user has all permissions without explicitly assigning them."))
|
||||
last_login = DateTimeField(default=datetime_now,
|
||||
verbose_name=_('last login'))
|
||||
date_joined = DateTimeField(default=datetime_now,
|
||||
verbose_name=_('date joined'))
|
||||
|
||||
user_permissions = ListField(ReferenceField(Permission), verbose_name=_('user permissions'),
|
||||
help_text=_('Permissions for the user.'))
|
||||
|
||||
USERNAME_FIELD = 'username'
|
||||
REQUIRED_FIELDS = ['email']
|
||||
|
||||
meta = {
|
||||
'allow_inheritance': True,
|
||||
'indexes': [
|
||||
{'fields': ['username'], 'unique': True, 'sparse': True}
|
||||
]
|
||||
}
|
||||
|
||||
def __unicode__(self):
|
||||
return self.username
|
||||
|
||||
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.
|
||||
"""
|
||||
self.password = make_password(raw_password)
|
||||
self.save()
|
||||
return self
|
||||
|
||||
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.
|
||||
"""
|
||||
return check_password(raw_password, self.password)
|
||||
|
||||
@classmethod
|
||||
def create_user(cls, username, password, email=None):
|
||||
"""Create (and save) a new user with the given username, password and
|
||||
email address.
|
||||
"""
|
||||
now = datetime_now()
|
||||
|
||||
# Normalize the address by lowercasing the domain part of the email
|
||||
# address.
|
||||
if email is not None:
|
||||
try:
|
||||
email_name, domain_part = email.strip().split('@', 1)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
email = '@'.join([email_name, domain_part.lower()])
|
||||
|
||||
user = cls(username=username, email=email, date_joined=now)
|
||||
user.set_password(password)
|
||||
user.save()
|
||||
return user
|
||||
|
||||
def get_group_permissions(self, obj=None):
|
||||
"""
|
||||
Returns a list of permission strings that this user has through his/her
|
||||
groups. This method queries all available auth backends. If an object
|
||||
is passed in, only permissions matching this object are returned.
|
||||
"""
|
||||
permissions = set()
|
||||
for backend in auth.get_backends():
|
||||
if hasattr(backend, "get_group_permissions"):
|
||||
permissions.update(backend.get_group_permissions(self, obj))
|
||||
return permissions
|
||||
|
||||
def get_all_permissions(self, obj=None):
|
||||
return _user_get_all_permissions(self, obj)
|
||||
|
||||
def has_perm(self, perm, obj=None):
|
||||
"""
|
||||
Returns True if the user has the specified permission. This method
|
||||
queries all available auth backends, but returns immediately if any
|
||||
backend returns True. Thus, a user who has permission from a single
|
||||
auth backend is assumed to have permission in general. If an object is
|
||||
provided, permissions for this specific object are checked.
|
||||
"""
|
||||
|
||||
# Active superusers have all permissions.
|
||||
if self.is_active and self.is_superuser:
|
||||
return True
|
||||
|
||||
# Otherwise we need to check the backends.
|
||||
return _user_has_perm(self, perm, obj)
|
||||
|
||||
def has_module_perms(self, app_label):
|
||||
"""
|
||||
Returns True if the user has any permissions in the given app label.
|
||||
Uses pretty much the same logic as has_perm, above.
|
||||
"""
|
||||
# Active superusers have all permissions.
|
||||
if self.is_active and self.is_superuser:
|
||||
return True
|
||||
|
||||
return _user_has_module_perms(self, app_label)
|
||||
|
||||
def email_user(self, subject, message, from_email=None):
|
||||
"Sends an e-mail to this User."
|
||||
from django.core.mail import send_mail
|
||||
send_mail(subject, message, from_email, [self.email])
|
||||
|
||||
def get_profile(self):
|
||||
"""
|
||||
Returns site-specific profile for this user. Raises
|
||||
SiteProfileNotAvailable if this site does not allow profiles.
|
||||
"""
|
||||
if not hasattr(self, '_profile_cache'):
|
||||
from django.conf import settings
|
||||
if not getattr(settings, 'AUTH_PROFILE_MODULE', False):
|
||||
raise SiteProfileNotAvailable('You need to set AUTH_PROFILE_MO'
|
||||
'DULE in your project settings')
|
||||
try:
|
||||
app_label, model_name = settings.AUTH_PROFILE_MODULE.split('.')
|
||||
except ValueError:
|
||||
raise SiteProfileNotAvailable('app_label and model_name should'
|
||||
' be separated by a dot in the AUTH_PROFILE_MODULE set'
|
||||
'ting')
|
||||
|
||||
try:
|
||||
model = models.get_model(app_label, model_name)
|
||||
if model is None:
|
||||
raise SiteProfileNotAvailable('Unable to load the profile '
|
||||
'model, check AUTH_PROFILE_MODULE in your project sett'
|
||||
'ings')
|
||||
self._profile_cache = model._default_manager.using(self._state.db).get(user__id__exact=self.id)
|
||||
self._profile_cache.user = self
|
||||
except (ImportError, ImproperlyConfigured):
|
||||
raise SiteProfileNotAvailable
|
||||
return self._profile_cache
|
||||
|
||||
|
||||
class MongoEngineBackend(object):
|
||||
"""Authenticate using MongoEngine and mongoengine.django.auth.User.
|
||||
"""
|
||||
|
||||
supports_object_permissions = False
|
||||
supports_anonymous_user = False
|
||||
supports_inactive_user = False
|
||||
_user_doc = False
|
||||
|
||||
def authenticate(self, username=None, password=None):
|
||||
user = self.user_document.objects(username=username).first()
|
||||
if user:
|
||||
if password and user.check_password(password):
|
||||
backend = auth.get_backends()[0]
|
||||
user.backend = "%s.%s" % (backend.__module__, backend.__class__.__name__)
|
||||
return user
|
||||
return None
|
||||
|
||||
def get_user(self, user_id):
|
||||
return self.user_document.objects.with_id(user_id)
|
||||
|
||||
@property
|
||||
def user_document(self):
|
||||
if self._user_doc is False:
|
||||
from .mongo_auth.models import get_user_document
|
||||
self._user_doc = get_user_document()
|
||||
return self._user_doc
|
||||
|
||||
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()
|
@ -1,119 +0,0 @@
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from django.contrib.auth.models import UserManager
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db import models
|
||||
try:
|
||||
from django.utils.module_loading import import_module
|
||||
except ImportError:
|
||||
"""Handle older versions of Django"""
|
||||
from django.utils.importlib import import_module
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
__all__ = (
|
||||
'get_user_document',
|
||||
)
|
||||
|
||||
|
||||
MONGOENGINE_USER_DOCUMENT = getattr(
|
||||
settings, 'MONGOENGINE_USER_DOCUMENT', 'mongoengine.django.auth.User')
|
||||
|
||||
|
||||
def get_user_document():
|
||||
"""Get the user document class used for authentication.
|
||||
|
||||
This is the class defined in settings.MONGOENGINE_USER_DOCUMENT, which
|
||||
defaults to `mongoengine.django.auth.User`.
|
||||
|
||||
"""
|
||||
|
||||
name = MONGOENGINE_USER_DOCUMENT
|
||||
dot = name.rindex('.')
|
||||
module = import_module(name[:dot])
|
||||
return getattr(module, name[dot + 1:])
|
||||
|
||||
|
||||
class MongoUserManager(UserManager):
|
||||
"""A User manager wich allows the use of MongoEngine documents in Django.
|
||||
|
||||
To use the manager, you must tell django.contrib.auth to use MongoUser as
|
||||
the user model. In you settings.py, you need:
|
||||
|
||||
INSTALLED_APPS = (
|
||||
...
|
||||
'django.contrib.auth',
|
||||
'mongoengine.django.mongo_auth',
|
||||
...
|
||||
)
|
||||
AUTH_USER_MODEL = 'mongo_auth.MongoUser'
|
||||
|
||||
Django will use the model object to access the custom Manager, which will
|
||||
replace the original queryset with MongoEngine querysets.
|
||||
|
||||
By default, mongoengine.django.auth.User will be used to store users. You
|
||||
can specify another document class in MONGOENGINE_USER_DOCUMENT in your
|
||||
settings.py.
|
||||
|
||||
The User Document class has the same requirements as a standard custom user
|
||||
model: https://docs.djangoproject.com/en/dev/topics/auth/customizing/
|
||||
|
||||
In particular, the User Document class must define USERNAME_FIELD and
|
||||
REQUIRED_FIELDS.
|
||||
|
||||
`AUTH_USER_MODEL` has been added in Django 1.5.
|
||||
|
||||
"""
|
||||
|
||||
def contribute_to_class(self, model, name):
|
||||
super(MongoUserManager, self).contribute_to_class(model, name)
|
||||
self.dj_model = self.model
|
||||
self.model = get_user_document()
|
||||
|
||||
self.dj_model.USERNAME_FIELD = self.model.USERNAME_FIELD
|
||||
username = models.CharField(_('username'), max_length=30, unique=True)
|
||||
username.contribute_to_class(self.dj_model, self.dj_model.USERNAME_FIELD)
|
||||
|
||||
self.dj_model.REQUIRED_FIELDS = self.model.REQUIRED_FIELDS
|
||||
for name in self.dj_model.REQUIRED_FIELDS:
|
||||
field = models.CharField(_(name), max_length=30)
|
||||
field.contribute_to_class(self.dj_model, name)
|
||||
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
try:
|
||||
return self.get_query_set().get(*args, **kwargs)
|
||||
except self.model.DoesNotExist:
|
||||
# ModelBackend expects this exception
|
||||
raise self.dj_model.DoesNotExist
|
||||
|
||||
@property
|
||||
def db(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_empty_query_set(self):
|
||||
return self.model.objects.none()
|
||||
|
||||
def get_query_set(self):
|
||||
return self.model.objects
|
||||
|
||||
|
||||
class MongoUser(models.Model):
|
||||
""""Dummy user model for Django.
|
||||
|
||||
MongoUser is used to replace Django's UserManager with MongoUserManager.
|
||||
The actual user document class is mongoengine.django.auth.User or any
|
||||
other document class specified in MONGOENGINE_USER_DOCUMENT.
|
||||
|
||||
To get the user document class, use `get_user_document()`.
|
||||
|
||||
"""
|
||||
|
||||
objects = MongoUserManager()
|
||||
|
||||
class Meta:
|
||||
app_label = 'mongo_auth'
|
||||
|
||||
def set_password(self, password):
|
||||
"""Doesn't do anything, but works around the issue with Django 1.6."""
|
||||
make_password(password)
|
@ -1,124 +0,0 @@
|
||||
from bson import json_util
|
||||
from django.conf import settings
|
||||
from django.contrib.sessions.backends.base import SessionBase, CreateError
|
||||
from django.core.exceptions import SuspiciousOperation
|
||||
try:
|
||||
from django.utils.encoding import force_unicode
|
||||
except ImportError:
|
||||
from django.utils.encoding import force_text as force_unicode
|
||||
|
||||
from mongoengine.document import Document
|
||||
from mongoengine import fields
|
||||
from mongoengine.queryset import OperationError
|
||||
from mongoengine.connection import DEFAULT_CONNECTION_NAME
|
||||
|
||||
from .utils import datetime_now
|
||||
|
||||
|
||||
MONGOENGINE_SESSION_DB_ALIAS = getattr(
|
||||
settings, 'MONGOENGINE_SESSION_DB_ALIAS',
|
||||
DEFAULT_CONNECTION_NAME)
|
||||
|
||||
# a setting for the name of the collection used to store sessions
|
||||
MONGOENGINE_SESSION_COLLECTION = getattr(
|
||||
settings, 'MONGOENGINE_SESSION_COLLECTION',
|
||||
'django_session')
|
||||
|
||||
# a setting for whether session data is stored encoded or not
|
||||
MONGOENGINE_SESSION_DATA_ENCODE = getattr(
|
||||
settings, 'MONGOENGINE_SESSION_DATA_ENCODE',
|
||||
True)
|
||||
|
||||
|
||||
class MongoSession(Document):
|
||||
session_key = fields.StringField(primary_key=True, max_length=40)
|
||||
session_data = fields.StringField() if MONGOENGINE_SESSION_DATA_ENCODE \
|
||||
else fields.DictField()
|
||||
expire_date = fields.DateTimeField()
|
||||
|
||||
meta = {
|
||||
'collection': MONGOENGINE_SESSION_COLLECTION,
|
||||
'db_alias': MONGOENGINE_SESSION_DB_ALIAS,
|
||||
'allow_inheritance': False,
|
||||
'indexes': [
|
||||
{
|
||||
'fields': ['expire_date'],
|
||||
'expireAfterSeconds': 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
def get_decoded(self):
|
||||
return SessionStore().decode(self.session_data)
|
||||
|
||||
|
||||
class SessionStore(SessionBase):
|
||||
"""A MongoEngine-based session store for Django.
|
||||
"""
|
||||
|
||||
def _get_session(self, *args, **kwargs):
|
||||
sess = super(SessionStore, self)._get_session(*args, **kwargs)
|
||||
if sess.get('_auth_user_id', None):
|
||||
sess['_auth_user_id'] = str(sess.get('_auth_user_id'))
|
||||
return sess
|
||||
|
||||
def load(self):
|
||||
try:
|
||||
s = MongoSession.objects(session_key=self.session_key,
|
||||
expire_date__gt=datetime_now)[0]
|
||||
if MONGOENGINE_SESSION_DATA_ENCODE:
|
||||
return self.decode(force_unicode(s.session_data))
|
||||
else:
|
||||
return s.session_data
|
||||
except (IndexError, SuspiciousOperation):
|
||||
self.create()
|
||||
return {}
|
||||
|
||||
def exists(self, session_key):
|
||||
return bool(MongoSession.objects(session_key=session_key).first())
|
||||
|
||||
def create(self):
|
||||
while True:
|
||||
self._session_key = self._get_new_session_key()
|
||||
try:
|
||||
self.save(must_create=True)
|
||||
except CreateError:
|
||||
continue
|
||||
self.modified = True
|
||||
self._session_cache = {}
|
||||
return
|
||||
|
||||
def save(self, must_create=False):
|
||||
if self.session_key is None:
|
||||
self._session_key = self._get_new_session_key()
|
||||
s = MongoSession(session_key=self.session_key)
|
||||
if MONGOENGINE_SESSION_DATA_ENCODE:
|
||||
s.session_data = self.encode(self._get_session(no_load=must_create))
|
||||
else:
|
||||
s.session_data = self._get_session(no_load=must_create)
|
||||
s.expire_date = self.get_expiry_date()
|
||||
try:
|
||||
s.save(force_insert=must_create)
|
||||
except OperationError:
|
||||
if must_create:
|
||||
raise CreateError
|
||||
raise
|
||||
|
||||
def delete(self, session_key=None):
|
||||
if session_key is None:
|
||||
if self.session_key is None:
|
||||
return
|
||||
session_key = self.session_key
|
||||
MongoSession.objects(session_key=session_key).delete()
|
||||
|
||||
|
||||
class BSONSerializer(object):
|
||||
"""
|
||||
Serializer that can handle BSON types (eg ObjectId).
|
||||
"""
|
||||
def dumps(self, obj):
|
||||
return json_util.dumps(obj, separators=(',', ':')).encode('ascii')
|
||||
|
||||
def loads(self, data):
|
||||
return json_util.loads(data.decode('ascii'))
|
||||
|
@ -1,47 +0,0 @@
|
||||
from mongoengine.queryset import QuerySet
|
||||
from mongoengine.base import BaseDocument
|
||||
from mongoengine.errors import ValidationError
|
||||
|
||||
def _get_queryset(cls):
|
||||
"""Inspired by django.shortcuts.*"""
|
||||
if isinstance(cls, QuerySet):
|
||||
return cls
|
||||
else:
|
||||
return cls.objects
|
||||
|
||||
def get_document_or_404(cls, *args, **kwargs):
|
||||
"""
|
||||
Uses get() to return an document, or raises a Http404 exception if the document
|
||||
does not exist.
|
||||
|
||||
cls may be a Document or QuerySet object. All other passed
|
||||
arguments and keyword arguments are used in the get() query.
|
||||
|
||||
Note: Like with get(), an MultipleObjectsReturned will be raised if more than one
|
||||
object is found.
|
||||
|
||||
Inspired by django.shortcuts.*
|
||||
"""
|
||||
queryset = _get_queryset(cls)
|
||||
try:
|
||||
return queryset.get(*args, **kwargs)
|
||||
except (queryset._document.DoesNotExist, ValidationError):
|
||||
from django.http import Http404
|
||||
raise Http404('No %s matches the given query.' % queryset._document._class_name)
|
||||
|
||||
def get_list_or_404(cls, *args, **kwargs):
|
||||
"""
|
||||
Uses filter() to return a list of documents, or raise a Http404 exception if
|
||||
the list is empty.
|
||||
|
||||
cls may be a Document or QuerySet object. All other passed
|
||||
arguments and keyword arguments are used in the filter() query.
|
||||
|
||||
Inspired by django.shortcuts.*
|
||||
"""
|
||||
queryset = _get_queryset(cls)
|
||||
obj_list = list(queryset.filter(*args, **kwargs))
|
||||
if not obj_list:
|
||||
from django.http import Http404
|
||||
raise Http404('No %s matches the given query.' % queryset._document._class_name)
|
||||
return obj_list
|
@ -1,112 +0,0 @@
|
||||
import os
|
||||
import itertools
|
||||
import urlparse
|
||||
|
||||
from mongoengine import *
|
||||
from django.conf import settings
|
||||
from django.core.files.storage import Storage
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
|
||||
class FileDocument(Document):
|
||||
"""A document used to store a single file in GridFS.
|
||||
"""
|
||||
file = FileField()
|
||||
|
||||
|
||||
class GridFSStorage(Storage):
|
||||
"""A custom storage backend to store files in GridFS
|
||||
"""
|
||||
|
||||
def __init__(self, base_url=None):
|
||||
|
||||
if base_url is None:
|
||||
base_url = settings.MEDIA_URL
|
||||
self.base_url = base_url
|
||||
self.document = FileDocument
|
||||
self.field = 'file'
|
||||
|
||||
def delete(self, name):
|
||||
"""Deletes the specified file from the storage system.
|
||||
"""
|
||||
if self.exists(name):
|
||||
doc = self.document.objects.first()
|
||||
field = getattr(doc, self.field)
|
||||
self._get_doc_with_name(name).delete() # Delete the FileField
|
||||
field.delete() # Delete the FileDocument
|
||||
|
||||
def exists(self, name):
|
||||
"""Returns True if a file referened by the given name already exists in the
|
||||
storage system, or False if the name is available for a new file.
|
||||
"""
|
||||
doc = self._get_doc_with_name(name)
|
||||
if doc:
|
||||
field = getattr(doc, self.field)
|
||||
return bool(field.name)
|
||||
else:
|
||||
return False
|
||||
|
||||
def listdir(self, path=None):
|
||||
"""Lists the contents of the specified path, returning a 2-tuple of lists;
|
||||
the first item being directories, the second item being files.
|
||||
"""
|
||||
def name(doc):
|
||||
return getattr(doc, self.field).name
|
||||
docs = self.document.objects
|
||||
return [], [name(d) for d in docs if name(d)]
|
||||
|
||||
def size(self, name):
|
||||
"""Returns the total size, in bytes, of the file specified by name.
|
||||
"""
|
||||
doc = self._get_doc_with_name(name)
|
||||
if doc:
|
||||
return getattr(doc, self.field).length
|
||||
else:
|
||||
raise ValueError("No such file or directory: '%s'" % name)
|
||||
|
||||
def url(self, name):
|
||||
"""Returns an absolute URL where the file's contents can be accessed
|
||||
directly by a web browser.
|
||||
"""
|
||||
if self.base_url is None:
|
||||
raise ValueError("This file is not accessible via a URL.")
|
||||
return urlparse.urljoin(self.base_url, name).replace('\\', '/')
|
||||
|
||||
def _get_doc_with_name(self, name):
|
||||
"""Find the documents in the store with the given name
|
||||
"""
|
||||
docs = self.document.objects
|
||||
doc = [d for d in docs if hasattr(getattr(d, self.field), 'name') and getattr(d, self.field).name == name]
|
||||
if doc:
|
||||
return doc[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
def _open(self, name, mode='rb'):
|
||||
doc = self._get_doc_with_name(name)
|
||||
if doc:
|
||||
return getattr(doc, self.field)
|
||||
else:
|
||||
raise ValueError("No file found with the name '%s'." % name)
|
||||
|
||||
def get_available_name(self, name):
|
||||
"""Returns a filename that's free on the target storage system, and
|
||||
available for new content to be written to.
|
||||
"""
|
||||
file_root, file_ext = os.path.splitext(name)
|
||||
# If the filename already exists, add an underscore and a number (before
|
||||
# the file extension, if one exists) to the filename until the generated
|
||||
# filename doesn't exist.
|
||||
count = itertools.count(1)
|
||||
while self.exists(name):
|
||||
# file_ext includes the dot.
|
||||
name = os.path.join("%s_%s%s" % (file_root, count.next(), file_ext))
|
||||
|
||||
return name
|
||||
|
||||
def _save(self, name, content):
|
||||
doc = self.document()
|
||||
getattr(doc, self.field).put(content, filename=name)
|
||||
doc.save()
|
||||
|
||||
return name
|
@ -1,31 +0,0 @@
|
||||
#coding: utf-8
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
from mongoengine import connect
|
||||
from mongoengine.connection import get_db
|
||||
|
||||
|
||||
class MongoTestCase(TestCase):
|
||||
"""
|
||||
TestCase class that clear the collection between the tests
|
||||
"""
|
||||
|
||||
@property
|
||||
def db_name(self):
|
||||
from django.conf import settings
|
||||
return 'test_%s' % getattr(settings, 'MONGO_DATABASE_NAME', 'dummy')
|
||||
|
||||
def __init__(self, methodName='runtest'):
|
||||
connect(self.db_name)
|
||||
self.db = get_db()
|
||||
super(MongoTestCase, self).__init__(methodName)
|
||||
|
||||
def dropCollections(self):
|
||||
for collection in self.db.collection_names():
|
||||
if collection.startswith('system.'):
|
||||
continue
|
||||
self.db.drop_collection(collection)
|
||||
|
||||
def tearDown(self):
|
||||
self.dropCollections()
|
@ -1,6 +0,0 @@
|
||||
try:
|
||||
# django >= 1.4
|
||||
from django.utils.timezone import now as datetime_now
|
||||
except ImportError:
|
||||
from datetime import datetime
|
||||
datetime_now = datetime.now
|
4
setup.py
4
setup.py
@ -53,12 +53,12 @@ CLASSIFIERS = [
|
||||
extra_opts = {"packages": find_packages(exclude=["tests", "tests.*"])}
|
||||
if sys.version_info[0] == 3:
|
||||
extra_opts['use_2to3'] = True
|
||||
extra_opts['tests_require'] = ['nose', 'coverage', 'blinker', 'jinja2==2.6', 'Pillow>=2.0.0', 'django>=1.5.1']
|
||||
extra_opts['tests_require'] = ['nose', 'coverage', 'blinker', 'Pillow>=2.0.0']
|
||||
if "test" in sys.argv or "nosetests" in sys.argv:
|
||||
extra_opts['packages'] = find_packages()
|
||||
extra_opts['package_data'] = {"tests": ["fields/mongoengine.png", "fields/mongodb_leaf.png"]}
|
||||
else:
|
||||
extra_opts['tests_require'] = ['nose', 'coverage', 'blinker', 'django>=1.4.2', 'Pillow>=2.0.0', 'jinja2>=2.6', 'python-dateutil']
|
||||
extra_opts['tests_require'] = ['nose', 'coverage', 'blinker', 'Pillow>=2.0.0', 'python-dateutil']
|
||||
|
||||
if sys.version_info[0] == 2 and sys.version_info[1] == 6:
|
||||
extra_opts['tests_require'].append('unittest2')
|
||||
|
@ -1,330 +0,0 @@
|
||||
import sys
|
||||
sys.path[0:0] = [""]
|
||||
import unittest
|
||||
from nose.plugins.skip import SkipTest
|
||||
|
||||
from mongoengine import *
|
||||
from mongoengine.django.shortcuts import get_document_or_404
|
||||
|
||||
import django
|
||||
from django.http import Http404
|
||||
from django.template import Context, Template
|
||||
from django.conf import settings
|
||||
from django.core.paginator import Paginator
|
||||
|
||||
settings.configure(
|
||||
USE_TZ=True,
|
||||
INSTALLED_APPS=('django.contrib.auth', 'mongoengine.django.mongo_auth'),
|
||||
AUTH_USER_MODEL=('mongo_auth.MongoUser'),
|
||||
AUTHENTICATION_BACKENDS = ('mongoengine.django.auth.MongoEngineBackend',)
|
||||
)
|
||||
|
||||
try:
|
||||
# For Django >= 1.7
|
||||
if hasattr(django, 'setup'):
|
||||
django.setup()
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from django.contrib.auth import authenticate, get_user_model
|
||||
from mongoengine.django.auth import User
|
||||
from mongoengine.django.mongo_auth.models import (
|
||||
MongoUser,
|
||||
MongoUserManager,
|
||||
get_user_document,
|
||||
)
|
||||
DJ15 = True
|
||||
except Exception:
|
||||
DJ15 = False
|
||||
from mongoengine.django.sessions import SessionStore, MongoSession
|
||||
from mongoengine.django.tests import MongoTestCase
|
||||
from datetime import tzinfo, timedelta
|
||||
ZERO = timedelta(0)
|
||||
|
||||
|
||||
class FixedOffset(tzinfo):
|
||||
"""Fixed offset in minutes east from UTC."""
|
||||
|
||||
def __init__(self, offset, name):
|
||||
self.__offset = timedelta(minutes=offset)
|
||||
self.__name = name
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return self.__offset
|
||||
|
||||
def tzname(self, dt):
|
||||
return self.__name
|
||||
|
||||
def dst(self, dt):
|
||||
return ZERO
|
||||
|
||||
|
||||
def activate_timezone(tz):
|
||||
"""Activate Django timezone support if it is available.
|
||||
"""
|
||||
try:
|
||||
from django.utils import timezone
|
||||
timezone.deactivate()
|
||||
timezone.activate(tz)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class QuerySetTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
connect(db='mongoenginetest')
|
||||
|
||||
class Person(Document):
|
||||
name = StringField()
|
||||
age = IntField()
|
||||
self.Person = Person
|
||||
|
||||
def test_order_by_in_django_template(self):
|
||||
"""Ensure that QuerySets are properly ordered in Django template.
|
||||
"""
|
||||
self.Person.drop_collection()
|
||||
|
||||
self.Person(name="A", age=20).save()
|
||||
self.Person(name="D", age=10).save()
|
||||
self.Person(name="B", age=40).save()
|
||||
self.Person(name="C", age=30).save()
|
||||
|
||||
t = Template("{% for o in ol %}{{ o.name }}-{{ o.age }}:{% endfor %}")
|
||||
|
||||
d = {"ol": self.Person.objects.order_by('-name')}
|
||||
self.assertEqual(t.render(Context(d)), u'D-10:C-30:B-40:A-20:')
|
||||
d = {"ol": self.Person.objects.order_by('+name')}
|
||||
self.assertEqual(t.render(Context(d)), u'A-20:B-40:C-30:D-10:')
|
||||
d = {"ol": self.Person.objects.order_by('-age')}
|
||||
self.assertEqual(t.render(Context(d)), u'B-40:C-30:A-20:D-10:')
|
||||
d = {"ol": self.Person.objects.order_by('+age')}
|
||||
self.assertEqual(t.render(Context(d)), u'D-10:A-20:C-30:B-40:')
|
||||
|
||||
self.Person.drop_collection()
|
||||
|
||||
def test_q_object_filter_in_template(self):
|
||||
|
||||
self.Person.drop_collection()
|
||||
|
||||
self.Person(name="A", age=20).save()
|
||||
self.Person(name="D", age=10).save()
|
||||
self.Person(name="B", age=40).save()
|
||||
self.Person(name="C", age=30).save()
|
||||
|
||||
t = Template("{% for o in ol %}{{ o.name }}-{{ o.age }}:{% endfor %}")
|
||||
|
||||
d = {"ol": self.Person.objects.filter(Q(age=10) | Q(name="C"))}
|
||||
self.assertEqual(t.render(Context(d)), 'D-10:C-30:')
|
||||
|
||||
# Check double rendering doesn't throw an error
|
||||
self.assertEqual(t.render(Context(d)), 'D-10:C-30:')
|
||||
|
||||
def test_get_document_or_404(self):
|
||||
p = self.Person(name="G404")
|
||||
p.save()
|
||||
|
||||
self.assertRaises(Http404, get_document_or_404, self.Person, pk='1234')
|
||||
self.assertEqual(p, get_document_or_404(self.Person, pk=p.pk))
|
||||
|
||||
def test_pagination(self):
|
||||
"""Ensure that Pagination works as expected
|
||||
"""
|
||||
class Page(Document):
|
||||
name = StringField()
|
||||
|
||||
Page.drop_collection()
|
||||
|
||||
for i in xrange(1, 11):
|
||||
Page(name=str(i)).save()
|
||||
|
||||
paginator = Paginator(Page.objects.all(), 2)
|
||||
|
||||
t = Template("{% for i in page.object_list %}{{ i.name }}:{% endfor %}")
|
||||
for p in paginator.page_range:
|
||||
d = {"page": paginator.page(p)}
|
||||
end = p * 2
|
||||
start = end - 1
|
||||
self.assertEqual(t.render(Context(d)), u'%d:%d:' % (start, end))
|
||||
|
||||
def test_nested_queryset_template_iterator(self):
|
||||
# Try iterating the same queryset twice, nested, in a Django template.
|
||||
names = ['A', 'B', 'C', 'D']
|
||||
|
||||
class CustomUser(Document):
|
||||
name = StringField()
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
CustomUser.drop_collection()
|
||||
|
||||
for name in names:
|
||||
CustomUser(name=name).save()
|
||||
|
||||
users = CustomUser.objects.all().order_by('name')
|
||||
template = Template("{% for user in users %}{{ user.name }}{% ifequal forloop.counter 2 %} {% for inner_user in users %}{{ inner_user.name }}{% endfor %} {% endifequal %}{% endfor %}")
|
||||
rendered = template.render(Context({'users': users}))
|
||||
self.assertEqual(rendered, 'AB ABCD CD')
|
||||
|
||||
def test_filter(self):
|
||||
"""Ensure that a queryset and filters work as expected
|
||||
"""
|
||||
|
||||
class LimitCountQuerySet(QuerySet):
|
||||
def count(self, with_limit_and_skip=True):
|
||||
return super(LimitCountQuerySet, self).count(with_limit_and_skip)
|
||||
|
||||
class Note(Document):
|
||||
meta = dict(queryset_class=LimitCountQuerySet)
|
||||
name = StringField()
|
||||
|
||||
Note.drop_collection()
|
||||
|
||||
for i in xrange(1, 101):
|
||||
Note(name="Note: %s" % i).save()
|
||||
|
||||
# Check the count
|
||||
self.assertEqual(Note.objects.count(), 100)
|
||||
|
||||
# Get the first 10 and confirm
|
||||
notes = Note.objects[:10]
|
||||
self.assertEqual(notes.count(), 10)
|
||||
|
||||
# Test djangos template filters
|
||||
# self.assertEqual(length(notes), 10)
|
||||
t = Template("{{ notes.count }}")
|
||||
c = Context({"notes": notes})
|
||||
self.assertEqual(t.render(c), "10")
|
||||
|
||||
# Test with skip
|
||||
notes = Note.objects.skip(90)
|
||||
self.assertEqual(notes.count(), 10)
|
||||
|
||||
# Test djangos template filters
|
||||
self.assertEqual(notes.count(), 10)
|
||||
t = Template("{{ notes.count }}")
|
||||
c = Context({"notes": notes})
|
||||
self.assertEqual(t.render(c), "10")
|
||||
|
||||
# Test with limit
|
||||
notes = Note.objects.skip(90)
|
||||
self.assertEqual(notes.count(), 10)
|
||||
|
||||
# Test djangos template filters
|
||||
self.assertEqual(notes.count(), 10)
|
||||
t = Template("{{ notes.count }}")
|
||||
c = Context({"notes": notes})
|
||||
self.assertEqual(t.render(c), "10")
|
||||
|
||||
# Test with skip and limit
|
||||
notes = Note.objects.skip(10).limit(10)
|
||||
|
||||
# Test djangos template filters
|
||||
self.assertEqual(notes.count(), 10)
|
||||
t = Template("{{ notes.count }}")
|
||||
c = Context({"notes": notes})
|
||||
self.assertEqual(t.render(c), "10")
|
||||
|
||||
|
||||
class _BaseMongoDBSessionTest(unittest.TestCase):
|
||||
backend = SessionStore
|
||||
|
||||
def setUp(self):
|
||||
connect(db='mongoenginetest')
|
||||
MongoSession.drop_collection()
|
||||
super(_BaseMongoDBSessionTest, self).setUp()
|
||||
|
||||
def assertIn(self, first, second, msg=None):
|
||||
self.assertTrue(first in second, msg)
|
||||
|
||||
def assertNotIn(self, first, second, msg=None):
|
||||
self.assertFalse(first in second, msg)
|
||||
|
||||
def test_first_save(self):
|
||||
session = SessionStore()
|
||||
session['test'] = True
|
||||
session.save()
|
||||
self.assertTrue('test' in session)
|
||||
|
||||
def test_session_expiration_tz(self):
|
||||
activate_timezone(FixedOffset(60, 'UTC+1'))
|
||||
# create and save new session
|
||||
session = SessionStore()
|
||||
session.set_expiry(600) # expire in 600 seconds
|
||||
session['test_expire'] = True
|
||||
session.save()
|
||||
# reload session with key
|
||||
key = session.session_key
|
||||
session = SessionStore(key)
|
||||
self.assertTrue('test_expire' in session, 'Session has expired before it is expected')
|
||||
|
||||
|
||||
try:
|
||||
# SessionTestsMixin isn't available for import on django > 1.8a1
|
||||
from django.contrib.sessions.tests import SessionTestsMixin
|
||||
|
||||
class _MongoDBSessionTest(SessionTestsMixin):
|
||||
pass
|
||||
|
||||
class MongoDBSessionTest(_BaseMongoDBSessionTest):
|
||||
pass
|
||||
|
||||
except ImportError:
|
||||
class MongoDBSessionTest(_BaseMongoDBSessionTest):
|
||||
pass
|
||||
|
||||
|
||||
class MongoAuthTest(unittest.TestCase):
|
||||
user_data = {
|
||||
'username': 'user',
|
||||
'email': 'user@example.com',
|
||||
'password': 'test',
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
if not DJ15:
|
||||
raise SkipTest('mongo_auth requires Django 1.5')
|
||||
connect(db='mongoenginetest')
|
||||
User.drop_collection()
|
||||
super(MongoAuthTest, self).setUp()
|
||||
|
||||
def test_get_user_model(self):
|
||||
self.assertEqual(get_user_model(), MongoUser)
|
||||
|
||||
def test_get_user_document(self):
|
||||
self.assertEqual(get_user_document(), User)
|
||||
|
||||
def test_user_manager(self):
|
||||
manager = get_user_model()._default_manager
|
||||
self.assertTrue(isinstance(manager, MongoUserManager))
|
||||
|
||||
def test_user_manager_exception(self):
|
||||
manager = get_user_model()._default_manager
|
||||
self.assertRaises(MongoUser.DoesNotExist, manager.get,
|
||||
username='not found')
|
||||
|
||||
def test_create_user(self):
|
||||
manager = get_user_model()._default_manager
|
||||
user = manager.create_user(**self.user_data)
|
||||
self.assertTrue(isinstance(user, User))
|
||||
db_user = User.objects.get(username='user')
|
||||
self.assertEqual(user.id, db_user.id)
|
||||
|
||||
def test_authenticate(self):
|
||||
get_user_model()._default_manager.create_user(**self.user_data)
|
||||
user = authenticate(username='user', password='fail')
|
||||
self.assertEqual(None, user)
|
||||
user = authenticate(username='user', password='test')
|
||||
db_user = User.objects.get(username='user')
|
||||
self.assertEqual(user.id, db_user.id)
|
||||
|
||||
|
||||
class MongoTestCaseTest(MongoTestCase):
|
||||
def test_mongo_test_case(self):
|
||||
self.db.dummy_collection.insert({'collection': 'will be dropped'})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -1,47 +0,0 @@
|
||||
import sys
|
||||
sys.path[0:0] = [""]
|
||||
|
||||
import unittest
|
||||
|
||||
from mongoengine import *
|
||||
|
||||
import jinja2
|
||||
|
||||
|
||||
class TemplateFilterTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
connect(db='mongoenginetest')
|
||||
|
||||
def test_jinja2(self):
|
||||
env = jinja2.Environment()
|
||||
|
||||
class TestData(Document):
|
||||
title = StringField()
|
||||
description = StringField()
|
||||
|
||||
TestData.drop_collection()
|
||||
|
||||
examples = [('A', '1'),
|
||||
('B', '2'),
|
||||
('C', '3')]
|
||||
|
||||
for title, description in examples:
|
||||
TestData(title=title, description=description).save()
|
||||
|
||||
tmpl = """
|
||||
{%- for record in content -%}
|
||||
{%- if loop.first -%}{ {%- endif -%}
|
||||
"{{ record.title }}": "{{ record.description }}"
|
||||
{%- if loop.last -%} }{%- else -%},{% endif -%}
|
||||
{%- endfor -%}
|
||||
"""
|
||||
ctx = {'content': TestData.objects}
|
||||
template = env.from_string(tmpl)
|
||||
rendered = template.render(**ctx)
|
||||
|
||||
self.assertEqual('{"A": "1","B": "2","C": "3"}', rendered)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Loading…
x
Reference in New Issue
Block a user