Refactor datastore implementation

This commit is contained in:
Matt Wright
2012-08-24 11:38:25 -04:00
parent da9f683c22
commit f928db298d
11 changed files with 167 additions and 153 deletions
+2 -2
View File
@@ -58,7 +58,7 @@ def generate_confirmation_token(user):
def requires_confirmation(user):
"""Returns `True` if the user requires confirmation."""
return user.confirmed_at == None and _security.confirmable
return _security.confirmable and user.confirmed_at == None
def confirm_email_token_status(token):
@@ -78,5 +78,5 @@ def confirm_user(user):
:param user: The user to confirm
"""
user.confirmed_at = datetime.utcnow()
_datastore._save_model(user)
_datastore.put(user)
user_confirmed.send(user, app=app._get_current_object())
+4 -2
View File
@@ -175,10 +175,12 @@ def _context_processor():
class RoleMixin(object):
"""Mixin for `Role` model definitions"""
def __eq__(self, other):
return self.name == other or self.name == getattr(other, 'name', None)
return (self.name == other or \
self.name == getattr(other, 'name', None))
def __ne__(self, other):
return self.name != other and self.name != getattr(other, 'name', None)
return (self.name != other and
self.name != getattr(other, 'name', None))
class UserMixin(BaseUserMixin):
+107 -127
View File
@@ -9,65 +9,52 @@
:license: MIT, see LICENSE for more details.
"""
class Datastore(object):
def __init__(self, db):
self.db = db
def commit(self):
pass
def put(self, model):
raise NotImplementedError
def delete(self, model):
raise NotImplementedError
class SQLAlchemyDatastore(Datastore):
def commit(self):
self.db.session.commit()
def put(self, model):
self.db.session.add(model)
return model
def delete(self, model):
self.db.session.delete(model)
class MongoEngineDatastore(Datastore):
def put(self, model):
model.save()
return model
def delete(self, model):
model.delete()
class UserDatastore(object):
"""Abstracted user datastore. Always extend this class and implement the
:attr:`_save_model`, :attr:`_delete_model`, :attr:`_do_find_user`, and
:attr:`_do_find_role` methods.
"""Abstracted user datastore.
:param db: An instance of a configured databse manager from a Flask
extension such as Flask-SQLAlchemy or Flask-MongoEngine
:param user_model: A user model class definition
:param role_model: A role model class definition
"""
def __init__(self, db, user_model, role_model):
self.db = db
def __init__(self, user_model, role_model):
self.user_model = user_model
self.role_model = role_model
def _commit(self, *args, **kwargs):
pass
def _save_model(self, model, **kwargs):
raise NotImplementedError(
"User datastore does not implement _save_model method")
def _delete_model(self, model):
raise NotImplementedError(
"User datastore does not implement _delete_model method")
def _do_find_user(self, **kwargs):
raise NotImplementedError(
"User datastore does not implement _do_find_user method")
def _do_find_role(self, **kwargs):
raise NotImplementedError(
"User datastore does not implement _do_find_role method")
def _do_add_role(self, user, role):
user, role = self._prepare_role_modify_args(user, role)
if role not in user.roles:
user.roles.append(role)
return user
def _do_remove_role(self, user, role):
user, role = self._prepare_role_modify_args(user, role)
if role in user.roles:
user.roles.remove(role)
return user
def _do_toggle_active(self, user, active):
user = self.find_user(email=user.email)
if active != user.active:
user.active = active
return user
def _do_deactive_user(self, user):
return self._do_toggle_active(user, False)
def _do_active_user(self, user):
return self._do_toggle_active(user, True)
def _prepare_role_modify_args(self, user, role):
role = role.name if isinstance(role, self.role_model) else role
return self.find_user(email=user.email), self.find_role(role)
@@ -75,124 +62,117 @@ class UserDatastore(object):
def _prepare_create_user_args(self, **kwargs):
kwargs.setdefault('active', True)
roles = kwargs.get('roles', [])
for i, role in enumerate(roles):
rn = role.name if isinstance(role, self.role_model) else role
# see if the role exists
roles[i] = self.find_role(rn)
kwargs['roles'] = roles
return kwargs
def find_user(self, **kwargs):
"""Returns a user based on the specified identifier.
"""Returns a user matching the provided paramters."""
raise NotImplementedError
:param user: User identifier, usually email address
def find_role(self, **kwargs):
"""Returns a role matching the provided paramters."""
raise NotImplementedError
def add_role_to_user(self, user, role):
"""Adds a role tp a user
:param user: The user to manipulate
:param role: The role to add to the user
"""
return self._do_find_user(**kwargs)
rv = False
user, role = self._prepare_role_modify_args(user, role)
if role not in user.roles:
rv = True
user.roles.append(role)
return rv
def find_role(self, role):
"""Returns a role based on its name.
def remove_role_from_user(self, user, role):
"""Removes a role from a user
:param role: Role name
:param user: The user to manipulate
:param role: The role to remove from the user
"""
return self._do_find_role(role)
rv = False
user, role = self._prepare_role_modify_args(user, role)
if role in user.roles:
rv = True
user.roles.remove(role)
return rv
def toggle_active(self, user):
"""Toggles a user's active status. Always returns True."""
user.active = not user.active
return True
def deactivate_user(self, user):
"""Deactivates a specified user. Returns `True` if a change was made.
:param user: The user to deactivate
"""
if user.active:
user.active = False
return True
return False
def activate_user(self, user):
"""Activates a specified user. Returns `True` if a change was made.
:param user: The user to activate
"""
if not user.active:
user.active = True
return True
return False
def create_role(self, **kwargs):
"""Creates and returns a new role.
"""Creates and returns a new role from the given parameters."""
:param name: Role name
"""
role = self.role_model(**kwargs)
return self._save_model(role)
return self.put(role)
def create_user(self, **kwargs):
"""Creates and returns a new user.
"""Creates and returns a new user from the given parameters."""
:param email: Email address
:param password: Unencrypted password
:param active: The optional active state
"""
user = self.user_model(**self._prepare_create_user_args(**kwargs))
return self._save_model(user)
return self.put(user)
def delete_user(self, user):
"""Delete the specified user
:param user: The user to delete_user
:param user: The user to delete
"""
self._delete_model(user)
def add_role_to_user(self, user, role):
"""Adds a role to a user if the user does not have it already. Returns
the modified user.
:param user: A User instance or a user identifier
:param role: A Role instance or a role name
"""
return self._save_model(self._do_add_role(user, role))
def remove_role_from_user(self, user, role):
"""Removes a role from a user if the user has the role. Returns the
modified user.
:param user: A User instance or a user identifier
:param role: A Role instance or a role name
"""
return self._save_model(self._do_remove_role(user, role))
def deactivate_user(self, user):
"""Deactivates a user and returns the modified user.
:param user: A User instance or a user identifier
"""
return self._save_model(self._do_deactive_user(user))
def activate_user(self, user):
"""Activates a user and returns the modified user.
:param user: A User instance or a user identifier
"""
return self._save_model(self._do_active_user(user))
self.delete(user)
class SQLAlchemyUserDatastore(UserDatastore):
class SQLAlchemyUserDatastore(SQLAlchemyDatastore, UserDatastore):
"""A SQLAlchemy datastore implementation for Flask-Security that assumes the
use of the Flask-SQLAlchemy extension.
"""
def __init__(self, db, user_model, role_model):
SQLAlchemyDatastore.__init__(self, db)
UserDatastore.__init__(self, user_model, role_model)
def _commit(self, *args, **kwargs):
self.db.session.commit()
def _save_model(self, model):
self.db.session.add(model)
return model
def _delete_model(self, model):
self.db.session.delete(model)
def _do_find_user(self, **kwargs):
def find_user(self, **kwargs):
return self.user_model.query.filter_by(**kwargs).first()
def _do_find_role(self, role):
def find_role(self, role):
return self.role_model.query.filter_by(name=role).first()
class MongoEngineUserDatastore(UserDatastore):
class MongoEngineUserDatastore(MongoEngineDatastore, UserDatastore):
"""A MongoEngine datastore implementation for Flask-Security that assumes
the use of the Flask-MongoEngine extension.
"""
def __init__(self, db, user_model, role_model):
MongoEngineDatastore.__init__(self, db)
UserDatastore.__init__(self, user_model, role_model)
def _save_model(self, model):
model.save()
return model
def _delete_model(self, model):
model.delete()
def _do_find_user(self, **kwargs):
def find_user(self, **kwargs):
return self.user_model.objects(**kwargs).first()
def _do_find_role(self, role):
def find_role(self, role):
return self.role_model.objects(name=role).first()
+1 -1
View File
@@ -75,6 +75,6 @@ def update_password(user, password):
:param password: The unencrypted new password
"""
user.password = encrypt_password(password)
_datastore._save_model(user)
_datastore.put(user)
send_password_reset_notice(user)
password_reset.send(user, app=app._get_current_object())
+1 -1
View File
@@ -26,7 +26,7 @@ def register_user(**kwargs):
confirmation_link, token = None, None
kwargs['password'] = encrypt_password(kwargs['password'])
user = _datastore.create_user(**kwargs)
_datastore._commit()
_datastore.commit()
if _security.confirmable:
confirmation_link, token = generate_confirmation_link(user)
+1 -1
View File
@@ -32,7 +32,7 @@ def pprint(obj):
def commit(fn):
def wrapper(*args, **kwargs):
fn(*args, **kwargs)
_datastore._commit()
_datastore.commit()
return wrapper
+1 -1
View File
@@ -64,7 +64,7 @@ def login_user(user, remember=True):
user.login_count = user.login_count + 1 if user.login_count else 1
_datastore._save_model(user)
_datastore.put(user)
identity_changed.send(current_app._get_current_object(),
identity=Identity(user.id))
+1 -1
View File
@@ -51,7 +51,7 @@ def _render_json(form):
def _commit(response=None):
_datastore._commit()
_datastore.commit()
return response
+1 -1
View File
@@ -182,7 +182,7 @@ class DefaultSecurityTests(SecurityTest):
with self.app.test_request_context('/'):
user = self.app.security.datastore.find_user(email='matt@lp.com')
self.app.security.datastore.delete_user(user)
self.app.security.datastore._commit()
self.app.security.datastore.commit()
r = self._get('/')
self.assertNotIn('Hello matt@lp.com', r.data)
+2 -3
View File
@@ -111,8 +111,7 @@ def create_app(config):
def create_roles():
for role in ('admin', 'editor', 'author'):
ds.create_role(name=role)
ds._commit()
ds.commit()
def create_users():
for u in (('matt@lp.com', 'password', ['admin'], True),
@@ -122,7 +121,7 @@ def create_users():
('tiya@lp.com', 'password', [], False)):
ds.create_user(email=u[0], password=encrypt_password(u[1]),
roles=u[2], active=u[3])
ds._commit()
ds.commit()
def populate_data():
create_roles()
+46 -13
View File
@@ -3,18 +3,16 @@
import unittest
from flask_security import RoleMixin, UserMixin, AnonymousUser
from flask_security.datastore import UserDatastore
from flask_security.datastore import Datastore, UserDatastore
class Role(RoleMixin):
def __init__(self, name, description=None):
def __init__(self, name):
self.name = name
self.description = description
class User(UserMixin):
def __init__(self, username, email, roles):
self.username = username
def __init__(self, email, roles):
self.email = email
self.roles = roles
@@ -22,7 +20,7 @@ admin = Role('admin')
admin2 = Role('admin')
editor = Role('editor')
user = User('matt', 'matt@lp.com', [admin, editor])
user = User('matt@lp.com', [admin, editor])
class SecurityEntityTests(unittest.TestCase):
@@ -45,11 +43,46 @@ class SecurityEntityTests(unittest.TestCase):
self.assertFalse(au.has_role('admin'))
class UserDatastoreTests(unittest.TestCase):
class DatastoreTests(unittest.TestCase):
def test_unimplemented(self):
ds = UserDatastore(None, None, None)
self.assertRaises(NotImplementedError, ds._save_model, None)
self.assertRaises(NotImplementedError, ds._delete_model, None)
self.assertRaises(NotImplementedError, ds._do_find_user)
self.assertRaises(NotImplementedError, ds._do_find_role)
def setUp(self):
super(DatastoreTests, self).setUp()
self.ds = UserDatastore(None, None)
def test_unimplemented_datastore_methods(self):
ds = Datastore(None)
self.assertRaises(NotImplementedError, ds.put, None)
self.assertRaises(NotImplementedError, ds.delete, None)
def test_unimplemented_user_datastore_methods(self):
self.assertRaises(NotImplementedError, self.ds.find_user)
self.assertRaises(NotImplementedError, self.ds.find_role)
def test_toggle_active(self):
user.active = True
rv = self.ds.toggle_active(user)
self.assertTrue(rv)
self.assertFalse(user.active)
rv = self.ds.toggle_active(user)
self.assertTrue(rv)
self.assertTrue(user.active)
def test_deactivate_user(self):
user.active = True
rv = self.ds.deactivate_user(user)
self.assertTrue(rv)
self.assertFalse(user.active)
def test_activate_user(self):
ds = UserDatastore(None, None)
user.active = False
ds.activate_user(user)
self.assertTrue(user.active)
def test_deactivate_returns_false_if_already_false(self):
user.active = False
self.assertFalse(self.ds.deactivate_user(user))
def test_activate_returns_false_if_already_true(self):
user.active = True
self.assertFalse(self.ds.activate_user(user))