Merge pull request #82 from maebert/flask-peewee

Flask-Peewee support
This commit is contained in:
Matt Wright
2013-02-01 14:44:52 -08:00
10 changed files with 255 additions and 20 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ python:
install:
- pip install . --quiet --use-mirrors
- "if [[ $TRAVIS_PYTHON_VERSION != '2.7' ]]; then pip install importlib --quiet --use-mirrors; fi"
- pip install nose simplejson Flask-SQLAlchemy Flask-MongoEngine Flask-Mail py-bcrypt MySQL-python --quiet --use-mirrors
- pip install nose simplejson Flask-SQLAlchemy Flask-MongoEngine Flask-Peewee Flask-Mail py-bcrypt MySQL-python --quiet --use-mirrors
before_script:
- mysql -e 'create database flask_security_test;'
+4
View File
@@ -49,6 +49,10 @@ Datastores
:members:
:inherited-members:
.. autoclass:: flask_security.datastore.PeeweeUserDatastore
:members:
:inherited-members:
Signals
-------
+6 -5
View File
@@ -10,7 +10,7 @@ Flask application. They include:
4. Basic HTTP authentication
5. Token based authentication
6. Token based account activation (optional)
7. Token based password recovery/resetting (optional)
7. Token based password recovery / resetting (optional)
8. User registration (optional)
9. Login tracking (optional)
@@ -27,10 +27,11 @@ and libraries. They include:
Additionally, it assumes you'll be using a common library for your database
connections and model definitions. Flask-Security supports the following Flask
extensions out of the box for data persistance:
extensions out of the box for data persistence:
1. `Flask-SQLAlchemy <http://packages.python.org/Flask-SQLAlchemy/>`_
2. `Flask-MongoEngine <http://packages.python.org/Flask-MongoEngine/>`_
1. `Flask-SQLAlchemy <http://pypi.python.org/pypi/flask-sqlalchemy/>`_
2. `Flask-MongoEngine <http://pypi.python.org/pypi/flask-mongoengine/>`_
3. `Flask-Peewee <http://pypi.python.org/pypi/flask-peewee/>`_
.. include:: contents.rst.inc
.. include:: contents.rst.inc
+7 -7
View File
@@ -1,12 +1,12 @@
Models
======
Flask-Security assumes you'll be using libraries such as SQLAlchemy or
MongoEngine to define a data model that includes a `User` and `Role` model. The
fields on your models must follow a particular convention depending on the
functionality your app requires. Aside from this, you're free to add any
additional fields to your model(s) if you want. At the bear minimum your `User`
and `Role` model should include the following fields:
Flask-Security assumes you'll be using libraries such as SQLAlchemy,
MongoEngine or Peewee to define a data model that includes a `User` and
`Role` model. The fields on your models must follow a particular convention
depending on the functionality your app requires. Aside from this, you're
free to add any additional fields to your model(s) if you want. At the bear
minimum your `User` and `Role` model should include the following fields:
**User**
@@ -48,4 +48,4 @@ additional fields:
* ``current_login_at``
* ``last_login_ip``
* ``current_login_ip``
* ``login_count``
* ``login_count``
+84 -3
View File
@@ -3,6 +3,7 @@ Quick Start
- `Basic SQLAlchemy Application <#basic-sqlalchemy-application>`_
- `Basic MongoEngine Application <#basic-mongoengine-application>`_
- `Basic Peewee Application <#basic-peewee-application>`_
- `Mail Configuration <#mail-configuration>`_
Basic SQLAlchemy Application
@@ -21,7 +22,7 @@ SQLAlchemy Application
~~~~~~~~~~~~~~~~~~~~~~
The following code sample illustrates how to get started as quickly as
possible using SQLAlchemy.
possible using SQLAlchemy:
::
@@ -94,7 +95,9 @@ MongoEngine Application
~~~~~~~~~~~~~~~~~~~~~~~
The following code sample illustrates how to get started as quickly as
possible using MongoEngine::
possible using MongoEngine:
::
from flask import Flask, render_template
from flask.ext.mongoengine import MongoEngine
@@ -144,6 +147,84 @@ possible using MongoEngine::
app.run()
Basic Peewee Application
========================
Peewee Install requirements
~~~~~~~~~~~~~~~~~~~~~~~~~~~
::
$ mkvirtualenv <your-app-name>
$ pip install flask-security flask-peewee
Peewee Application
~~~~~~~~~~~~~~~~~~
The following code sample illustrates how to get started as quickly as
possible using Peewee:
::
from flask import Flask, render_template
from flask_peewee.db import Database
from peewee import *
from flask.ext.security import Security, PeeweeUserDatastore, \
UserMixin, RoleMixin, login_required
# Create app
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SECRET_KEY'] = 'super-secret'
app.config['DATABASE'] = {
'name': 'example.db',
'engine': 'peewee.SqliteDatabase',
}
# Create database connection object
db = Database(app)
class Role(db.Model, RoleMixin):
name = TextField(unique=True)
description = TextField(null=True)
class User(db.Model, UserMixin):
email = TextField()
password = TextField()
active = BooleanField(default=True)
confirmed_at = DateTimeField(null=True)
class UserRoles(db.Model):
# Because peewee does not come with built-in many-to-many
# relationships, we need this intermediary class to link
# user to roles.
user = ForeignKeyField(User, related_name='roles')
role = ForeignKeyField(Role, related_name='users')
name = property(lambda self: self.role.name)
description = property(lambda self: self.role.description)
# Setup Flask-Security
user_datastore = PeeweeUserDatastore(db, User, Role, UserRoles)
security = Security(app, user_datastore)
# Create a user to test with
@app.before_first_request
def create_user():
for Model in (Role, User, UserRoles):
Model.drop_table(fail_silently=True)
Model.create_table(fail_silently=True)
user_datastore.create_user(email='matt@nobien.net', password='password')
# Views
@app.route('/')
@login_required
def home():
return render_template('index.html')
if __name__ == '__main__':
app.run()
Mail Configuration
===================
@@ -168,4 +249,4 @@ the basic application code in the previous section::
To learn more about the various Flask-Mail settings to configure it to
work with your particular email server configuration, please see the
`Flask-Mail documentation <http://packages.python.org/Flask-Mail/>`_.
`Flask-Mail documentation <http://packages.python.org/Flask-Mail/>`_.
+1 -1
View File
@@ -13,7 +13,7 @@
__version__ = '1.5.4'
from .core import Security, RoleMixin, UserMixin, AnonymousUser, current_user
from .datastore import SQLAlchemyUserDatastore, MongoEngineUserDatastore
from .datastore import SQLAlchemyUserDatastore, MongoEngineUserDatastore, PeeweeUserDatastore
from .decorators import auth_token_required, http_auth_required, \
login_required, roles_accepted, roles_required
from .forms import ForgotPasswordForm, LoginForm, RegisterForm, \
+82 -3
View File
@@ -44,6 +44,15 @@ class MongoEngineDatastore(Datastore):
model.delete()
class PeeweeDatastore(Datastore):
def put(self, model):
model.save()
return model
def delete(self, model):
model.delete_instance()
class UserDatastore(object):
"""Abstracted user datastore.
@@ -73,11 +82,11 @@ class UserDatastore(object):
return kwargs
def find_user(self, **kwargs):
"""Returns a user matching the provided paramters."""
"""Returns a user matching the provided parameters."""
raise NotImplementedError
def find_role(self, **kwargs):
"""Returns a role matching the provided paramters."""
def find_role(self, name):
"""Returns a role matching the provided name."""
raise NotImplementedError
def add_role_to_user(self, user, role):
@@ -137,6 +146,13 @@ class UserDatastore(object):
role = self.role_model(**kwargs)
return self.put(role)
def find_or_create_role(self, name, **kwargs):
"""Returns a role matching the given name or creates it with any
additionally provided parameters
"""
kwargs["name"] = name
return self.find_role(name) or self.create_role(**kwargs)
def create_user(self, **kwargs):
"""Creates and returns a new user from the given parameters."""
@@ -179,3 +195,66 @@ class MongoEngineUserDatastore(MongoEngineDatastore, UserDatastore):
def find_role(self, role):
return self.role_model.objects(name=role).first()
class PeeweeUserDatastore(PeeweeDatastore, UserDatastore):
"""A PeeweeD datastore implementation for Flask-Security that assumes
the use of the Flask-Peewee extension.
:param user_model: A user model class definition
:param role_model: A role model class definition
:param role_link: A model implementing the many-to-many user-role relation
"""
def __init__(self, db, user_model, role_model, role_link):
PeeweeDatastore.__init__(self, db)
UserDatastore.__init__(self, user_model, role_model)
self.UserRole = role_link
def find_user(self, **kwargs):
try:
return self.user_model.filter(**kwargs).get()
except self.user_model.DoesNotExist:
return None
def find_role(self, role):
try:
return self.role_model.filter(name=role).get()
except self.role_model.DoesNotExist:
return None
def create_user(self, **kwargs):
"""Creates and returns a new user from the given parameters."""
roles = kwargs.pop('roles', [])
user = self.user_model(**self._prepare_create_user_args(**kwargs))
user = self.put(user)
for role in roles:
self.add_role_to_user(user, role)
return user
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
"""
user, role = self._prepare_role_modify_args(user, role)
if self.UserRole.select().where(self.UserRole.user==user, self.UserRole.role==role).count():
return False
else:
self.UserRole.create(user=user, role=role)
return True
def remove_role_from_user(self, user, role):
"""Removes a role from a user
:param user: The user to manipulate
:param role: The role to remove from the user
"""
user, role = self._prepare_role_modify_args(user, role)
if self.UserRole.select().where(self.UserRole.user==user, self.UserRole.role==role).count():
self.UserRole.delete().where(self.UserRole.user==user, self.UserRole.role==role)
return True
else:
return False
+1
View File
@@ -47,6 +47,7 @@ setup(
'nose',
'Flask-SQLAlchemy',
'Flask-MongoEngine',
'Flask-Peewee',
'py-bcrypt',
'simplejson'
],
+7
View File
@@ -224,6 +224,13 @@ class MongoEngineSecurityTests(DefaultSecurityTests):
return create_app(auth_config, **kwargs)
class PeeweeSecurityTests(DefaultSecurityTests):
def _create_app(self, auth_config, **kwargs):
from tests.test_app.peewee_app import create_app
return create_app(auth_config, **kwargs)
class DefaultDatastoreTests(SecurityTest):
def test_add_role_to_user(self):
+62
View File
@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
import sys
import os
sys.path.pop(0)
sys.path.insert(0, os.getcwd())
from flask_peewee.db import Database
from peewee import *
from flask.ext.security import Security, UserMixin, RoleMixin, \
PeeweeUserDatastore
from tests.test_app import create_app as create_base_app, populate_data, \
add_context_processors
def create_app(config, **kwargs):
app = create_base_app(config)
app.config['DATABASE'] = {
'name': 'example2.db',
'engine': 'peewee.SqliteDatabase',
}
db = Database(app)
class Role(db.Model, RoleMixin):
name = TextField(unique=True)
description = TextField(null=True)
class User(db.Model, UserMixin):
email = TextField()
password = TextField()
last_login_at = DateTimeField(null=True)
current_login_at = DateTimeField(null=True)
last_login_ip = TextField(null=True)
current_login_ip = TextField(null=True)
login_count = IntegerField(null=True)
active = BooleanField(default=True)
confirmed_at = DateTimeField(null=True)
class UserRoles(db.Model):
""" Peewee does not have built-in many-to-many support, so we have to
create this mapping class to link users to roles."""
user = ForeignKeyField(User, related_name='roles')
role = ForeignKeyField(Role, related_name='users')
name = property(lambda self: self.role.name)
description = property(lambda self: self.role.description)
@app.before_first_request
def before_first_request():
for Model in (Role, User, UserRoles):
Model.drop_table(fail_silently=True)
Model.create_table(fail_silently=True)
populate_data(app.config.get('USER_COUNT', None))
app.security = Security(app, datastore=PeeweeUserDatastore(db, User, Role, UserRoles), **kwargs)
add_context_processors(app.security)
return app
if __name__ == '__main__':
create_app({}).run()