diff --git a/{{cookiecutter.app_name}}/tests/test_models.py b/{{cookiecutter.app_name}}/tests/test_models.py index 1951c1a..882d7f6 100644 --- a/{{cookiecutter.app_name}}/tests/test_models.py +++ b/{{cookiecutter.app_name}}/tests/test_models.py @@ -10,6 +10,13 @@ from .factories import UserFactory @pytest.mark.usefixtures('db') class TestUser: + def test_get_by_id(self): + user = User('foo', 'foo@bar.com') + user.save() + + retrieved = User.get_by_id(user.id) + assert retrieved == user + def test_created_at_defaults_to_datetime(self): user = User(username='foo', email='foo@bar.com') user.save() diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/database.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/database.py index 296f29a..6dbff38 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/database.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/database.py @@ -6,6 +6,7 @@ from sqlalchemy.orm import relationship from .extensions import db +# Alias common SQLAlchemy names Column = db.Column relationship = relationship @@ -13,18 +14,6 @@ class CRUDMixin(object): """Mixin that adds convenience methods for CRUD (create, read, update, delete) operations. """ - __table_args__ = {'extend_existing': True} - - id = db.Column(db.Integer, primary_key=True) - - @classmethod - def get_by_id(cls, id): - if any( - (isinstance(id, basestring) and id.isdigit(), - isinstance(id, (int, float))), - ): - return cls.query.get(int(id)) - return None @classmethod def create(cls, **kwargs): @@ -50,10 +39,38 @@ class CRUDMixin(object): db.session.delete(self) return commit and db.session.commit() +class Model(CRUDMixin, db.Model): + """Base model class that includes CRUD convenience methods.""" + __abstract__ = True + # From Mike Bayer's "Building the app" talk # https://speakerdeck.com/zzzeek/building-the-app -def ReferenceCol(tablename, nullable=False, **kwargs): - """Column that adds primary key foreign key reference.""" +class SurrogatePK(object): + """A mixin that adds a surrogate integer 'primary key' column named + ``id`` to any declarative-mapped class. + """ + __table_args__ = {'extend_existing': True} + + id = db.Column(db.Integer, primary_key=True) + + @classmethod + def get_by_id(cls, id): + if any( + (isinstance(id, basestring) and id.isdigit(), + isinstance(id, (int, float))), + ): + return cls.query.get(int(id)) + return None + + +def ReferenceCol(tablename, nullable=False, pk_name='id', **kwargs): + """Column that adds primary key foreign key reference. + + Usage: :: + + category_id = ReferenceCol('category') + category = relationship('Category', backref='categories') + """ return db.Column( - db.ForeignKey("{0}.id".format(tablename)), + db.ForeignKey("{0}.{1}".format(tablename, pk_name)), nullable=nullable, **kwargs) diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/models.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/models.py index b93509f..5eb8ce8 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/models.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/models.py @@ -5,15 +5,16 @@ from flask.ext.login import UserMixin from {{cookiecutter.app_name}}.extensions import bcrypt from {{cookiecutter.app_name}}.database import ( + Column, db, - CRUDMixin, + Model, ReferenceCol, relationship, - Column, + SurrogatePK, ) -class Role(CRUDMixin, db.Model): +class Role(SurrogatePK, Model): __tablename__ = 'roles' name = Column(db.String(80), unique=True, nullable=False) user_id = ReferenceCol('users', nullable=True) @@ -22,7 +23,7 @@ class Role(CRUDMixin, db.Model): def __init__(self, name, **kwargs): db.Model.__init__(self, name=name, **kwargs) -class User(UserMixin, CRUDMixin, db.Model): +class User(UserMixin, SurrogatePK, Model): __tablename__ = 'users' username = Column(db.String(80), unique=True, nullable=False)