diff --git a/README.rst b/README.rst
index d250497..db2a0f0 100644
--- a/README.rst
+++ b/README.rst
@@ -13,7 +13,7 @@ Features
- Flask-WTForms with login and registration forms
- Flask-Login for authentication
- Procfile for deploying to a PaaS (e.g. Heroku)
-- Flask-Testing and nose for testing
+- Flask-Testing, nose, and Factory-Boy for testing
- A simple ``manage.py`` script.
- CSS and JS minification using Flask-Assets
- Utilizes best practices: `Blueprints `_ and `Application Factory `_ patterns
@@ -69,6 +69,7 @@ Changelog
- Use Flask-Bcrypt for password hashing.
- Flask-Testing support.
- Flask-Login for authentication.
+- Use Factory-Boy for test factories.
0.2.0 (09/21/2013)
******************
diff --git a/{{cookiecutter.repo_name}}/requirements/dev.txt b/{{cookiecutter.repo_name}}/requirements/dev.txt
index aa257a0..d339fdc 100644
--- a/{{cookiecutter.repo_name}}/requirements/dev.txt
+++ b/{{cookiecutter.repo_name}}/requirements/dev.txt
@@ -4,6 +4,7 @@
# Testing
nose
Flask-Testing
+factory-boy>=2.2.1
# Management script
Flask-Script
diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/app.py b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/app.py
index 89066d5..751fd9b 100644
--- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/app.py
+++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/app.py
@@ -3,7 +3,7 @@ from flask import Flask
from flask.ext.assets import Environment
from webassets.loaders import PythonLoader
-from {{ cookiecutter.repo_name }}.settings import ProdConfig
+from {{cookiecutter.repo_name}}.settings import ProdConfig
from {{cookiecutter.repo_name}}.assets import assets
from {{cookiecutter.repo_name}}.extensions import login_manager
from {{cookiecutter.repo_name}}.database import db
diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/settings.py b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/settings.py
index 4727f8f..df862f2 100644
--- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/settings.py
+++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/settings.py
@@ -5,6 +5,7 @@ class Config(object):
SECRET_KEY = 'shhhh'
APP_DIR = os.path.abspath(os.path.dirname(__file__)) # This directory
PROJECT_ROOT = os.path.abspath(os.path.join(APP_DIR, os.pardir))
+ BCRYPT_LEVEL = 13
class ProdConfig(Config):
ENV = 'prod'
diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/tests/factories.py b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/tests/factories.py
new file mode 100644
index 0000000..6c15162
--- /dev/null
+++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/tests/factories.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+from factory import Sequence, PostGenerationMethodCall
+from factory.alchemy import SQLAlchemyModelFactory
+
+from {{cookiecutter.repo_name}}.user.models import User
+from {{cookiecutter.repo_name}}.database import db
+
+
+class UserFactory(SQLAlchemyModelFactory):
+ FACTORY_SESSION = db.session
+ FACTORY_FOR = User
+
+ username = Sequence(lambda n: "user{0}".format(n))
+ email = Sequence(lambda n: "user{0}@example.com".format(n))
+ password = PostGenerationMethodCall("set_password", 'example')
diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/tests/test_models.py b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/tests/test_models.py
index f2b8547..ab1decb 100644
--- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/tests/test_models.py
+++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/tests/test_models.py
@@ -6,6 +6,7 @@ from flask.ext.testing import TestCase
from {{ cookiecutter.repo_name }}.app import create_app
from {{ cookiecutter.repo_name }}.database import db
from {{ cookiecutter.repo_name }}.user.models import User
+from .factories import UserFactory
class TestUser(TestCase):
@@ -23,6 +24,15 @@ class TestUser(TestCase):
db.session.remove()
db.drop_all()
+ def test_factory(self):
+ user = UserFactory(password="myprecious")
+ assert_true(user.username)
+ assert_true(user.email)
+ assert_true(user.created_at)
+ assert_false(user.is_admin)
+ assert_false(user.active)
+ assert_true(user.check_password("myprecious"))
+
def test_check_password(self):
user = User(username="foo", email="foo@bar.com",
password="foobarbaz123")
diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/user/models.py b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/user/models.py
index 930200e..c8f73d6 100644
--- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/user/models.py
+++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/user/models.py
@@ -12,24 +12,26 @@ class User(UserMixin, CRUDMixin, db.Model):
__tablename__ = 'users'
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(80), unique=True, nullable=False)
- password_hash = db.Column(db.String, nullable=False)
+ password = db.Column(db.String, nullable=False) # The hashed password
created_at = db.Column(db.DateTime(), nullable=False)
active = db.Column(db.Boolean())
is_admin = db.Column(db.Boolean())
- def __init__(self, username, email, password, active=False, is_admin=False):
+ def __init__(self, username=None, email=None, password=None,
+ active=False, is_admin=False):
self.username = username
self.email = email
- self.set_password(password)
+ if password:
+ self.set_password(password)
self.active = active
self.is_admin = is_admin
self.created_at = dt.datetime.utcnow()
def set_password(self, password):
- self.password_hash = bcrypt.generate_password_hash(password)
+ self.password = bcrypt.generate_password_hash(password)
def check_password(self, password):
- return bcrypt.check_password_hash(self.password_hash, password)
+ return bcrypt.check_password_hash(self.password, password)
def __repr__(self):
return ''.format(username=self.username)