Add PasswordType and make __init__ less verbose

master
Steven Loria 10 years ago
parent 41824c41cc
commit 388cf106c2
  1. 57
      {{cookiecutter.app_name}}/{{cookiecutter.app_name}}/database.py
  2. 2
      {{cookiecutter.app_name}}/{{cookiecutter.app_name}}/tests/factories.py
  3. 18
      {{cookiecutter.app_name}}/{{cookiecutter.app_name}}/tests/test_models.py
  4. 45
      {{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/models.py

@ -2,10 +2,12 @@
"""Database module, including the SQLAlchemy database object and DB-related
utilities.
"""
from sqlalchemy.orm import relationship
from .extensions import db
from sqlalchemy.types import TypeDecorator
from .extensions import db, bcrypt
Column = db.Column
relationship = relationship
class CRUDMixin(object):
@ -49,9 +51,58 @@ class CRUDMixin(object):
db.session.delete(self)
return commit and db.session.commit()
# From Mike Bayer's "atmcraft" example app
# https://speakerdeck.com/zzzeek/building-the-app
def ReferenceCol(tablename, nullable=False, **kwargs):
"""Column that adds primary key foreign key reference."""
return db.Column(
db.ForeignKey("{0}.id".format(tablename)),
nullable=nullable, **kwargs)
class Password(str):
"""Coerce a string to a bcrypt password.
Rationale: for an easy string comparison,
so we can say ``some_password == 'hello123'``
.. seealso::
https://pypi.python.org/pypi/bcrypt/
"""
def __new__(cls, value, crypt=True):
if value is None:
return None
if isinstance(value, unicode):
value = value.encode('utf-8')
if crypt:
value = bcrypt.generate_password_hash(value)
return str.__new__(cls, value)
def __eq__(self, other):
if other and not isinstance(other, Password):
return bcrypt.check_password_hash(self, other)
return str.__eq__(self, other)
def __ne__(self, other):
if other and not isinstance(other, Password):
return bcrypt.check_password_hash(self, other)
return not self.__eq__(other)
class BcryptType(TypeDecorator):
"""Coerce strings to bcrypted Password objects for the database.
"""
impl = db.String(128)
def process_bind_param(self, value, dialect):
return Password(value)
def process_result_value(self, value, dialect):
# already crypted, so don't crypt again
return Password(value, crypt=False)
def __repr__(self):
return "BcryptType()"

@ -12,5 +12,5 @@ class UserFactory(SQLAlchemyModelFactory):
username = Sequence(lambda n: "user{0}".format(n))
email = Sequence(lambda n: "user{0}@example.com".format(n))
password = PostGenerationMethodCall("set_password", 'example')
password = 'example'
active = True

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
import unittest
import datetime as dt
from nose.tools import * # PEP8 asserts
from {{ cookiecutter.app_name }}.database import db
@ -10,6 +11,17 @@ from .factories import UserFactory
class TestUser(DbTestCase):
def test_created_at_defaults_to_utcnow(self):
user = User(username='foo', email='foo@bar.com')
user.save()
assert_true(user.created_at)
assert_true(isinstance(user.created_at, dt.datetime))
def test_password_is_nullable(self):
user = User(username='foo', email='foo@bar.com')
user.save()
assert_is(user.password, None)
def test_factory(self):
user = UserFactory(password="myprecious")
assert_true(user.username)
@ -17,13 +29,13 @@ class TestUser(DbTestCase):
assert_true(user.created_at)
assert_false(user.is_admin)
assert_true(user.active)
assert_true(user.check_password("myprecious"))
assert_true(user.password == "myprecious")
def test_check_password(self):
user = User.create(username="foo", email="foo@bar.com",
password="foobarbaz123")
assert_true(user.check_password('foobarbaz123'))
assert_false(user.check_password("barfoobaz"))
assert_true(user.password == 'foobarbaz123')
assert_false(user.password != "barfoobaz")
def test_full_name(self):
user = UserFactory(first_name="Foo", last_name="Bar")

@ -8,47 +8,34 @@ from {{cookiecutter.app_name}}.database import (
CRUDMixin,
ReferenceCol,
relationship,
Column,
BcryptType,
)
from {{cookiecutter.app_name}}.extensions import bcrypt
class Role(CRUDMixin, db.Model):
__tablename__ = 'roles'
name = db.Column(db.String(80), unique=True, nullable=False)
name = Column(db.String(80), unique=True, nullable=False)
user_id = ReferenceCol('users', nullable=True)
user = relationship('User', backref='roles')
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 = db.Column(db.String, nullable=False) # The hashed password
created_at = db.Column(db.DateTime(), nullable=False)
first_name = db.Column(db.String(30), nullable=True)
last_name = db.Column(db.String(30), nullable=True)
active = db.Column(db.Boolean())
is_admin = db.Column(db.Boolean())
def __init__(self, username=None, email=None, password=None,
first_name=None, last_name=None,
active=False, is_admin=False):
self.username = username
self.email = email
if password:
self.set_password(password)
self.active = active
self.is_admin = is_admin
self.created_at = dt.datetime.utcnow()
self.first_name = first_name
self.last_name = last_name
def set_password(self, password):
self.password = bcrypt.generate_password_hash(password)
def check_password(self, password):
return bcrypt.check_password_hash(self.password, password)
username = Column(db.String(80), unique=True, nullable=False)
email = Column(db.String(80), unique=True, nullable=False)
password = Column(BcryptType, nullable=True)
created_at = Column(db.DateTime, nullable=False)
first_name = Column(db.String(30), nullable=True)
last_name = Column(db.String(30), nullable=True)
active = Column(db.Boolean(), default=False)
is_admin = Column(db.Boolean(), default=False)
def __init__(self, username, email, **kwargs):
db.Model.__init__(self, username=username, email=email, **kwargs)
if not self.created_at:
self.created_at = dt.datetime.utcnow()
@property
def full_name(self):

Loading…
Cancel
Save