diff options
| author | Michael Merickel <michael@merickel.org> | 2016-02-18 02:39:43 -0600 |
|---|---|---|
| committer | Michael Merickel <michael@merickel.org> | 2016-02-18 02:39:43 -0600 |
| commit | 5dc1c80046b7eb83fb7c51105bed0e73b2ab759c (patch) | |
| tree | a66d8ee8f8282a72a5ada4fc5d903486caac6a85 /docs/tutorials/wiki2/src/models | |
| parent | 3eb1c354d320536ee470b79dcb930d20da93d97d (diff) | |
| parent | 66fabb4ac707b5b4289db0094756f1a1af7269cc (diff) | |
| download | pyramid-5dc1c80046b7eb83fb7c51105bed0e73b2ab759c.tar.gz pyramid-5dc1c80046b7eb83fb7c51105bed0e73b2ab759c.tar.bz2 pyramid-5dc1c80046b7eb83fb7c51105bed0e73b2ab759c.zip | |
Merge pull request #2334 from mmerickel/feature/alchemy-scaffold-update-tweaks
object-level security and tons of other small improvements to the wiki2 tutorial
Diffstat (limited to 'docs/tutorials/wiki2/src/models')
15 files changed, 185 insertions, 80 deletions
diff --git a/docs/tutorials/wiki2/src/models/MANIFEST.in b/docs/tutorials/wiki2/src/models/MANIFEST.in index 81beba1b1..42cd299b5 100644 --- a/docs/tutorials/wiki2/src/models/MANIFEST.in +++ b/docs/tutorials/wiki2/src/models/MANIFEST.in @@ -1,2 +1,2 @@ include *.txt *.ini *.cfg *.rst -recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml +recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.jinja2 *.pt *.txt *.mak *.mako *.js *.html *.xml diff --git a/docs/tutorials/wiki2/src/models/production.ini b/docs/tutorials/wiki2/src/models/production.ini index 97acfbd7d..cb1db3211 100644 --- a/docs/tutorials/wiki2/src/models/production.ini +++ b/docs/tutorials/wiki2/src/models/production.ini @@ -11,8 +11,6 @@ pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en -pyramid.includes = - pyramid_tm sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite diff --git a/docs/tutorials/wiki2/src/models/setup.py b/docs/tutorials/wiki2/src/models/setup.py index eb771010f..bdc9ceed7 100644 --- a/docs/tutorials/wiki2/src/models/setup.py +++ b/docs/tutorials/wiki2/src/models/setup.py @@ -9,6 +9,7 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: CHANGES = f.read() requires = [ + 'bcrypt', 'pyramid', 'pyramid_jinja2', 'pyramid_debugtoolbar', @@ -19,6 +20,10 @@ requires = [ 'waitress', ] +tests_require = [ + 'WebTest', +] + setup(name='tutorial', version='0.0', description='tutorial', @@ -37,6 +42,7 @@ setup(name='tutorial', include_package_data=True, zip_safe=False, test_suite='tutorial', + tests_require=tests_require, install_requires=requires, entry_points="""\ [paste.app_factory] diff --git a/docs/tutorials/wiki2/src/models/tutorial/__init__.py b/docs/tutorials/wiki2/src/models/tutorial/__init__.py index 7994bbfa8..4dab44823 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/models/tutorial/__init__.py @@ -6,8 +6,7 @@ def main(global_config, **settings): """ config = Configurator(settings=settings) config.include('pyramid_jinja2') - config.include('.models.meta') - config.add_static_view('static', 'static', cache_max_age=3600) - config.add_route('home', '/') + config.include('.models') + config.include('.routes') config.scan() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py index 7b1c62867..a8871f6f5 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py +++ b/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py @@ -1,7 +1,74 @@ +from sqlalchemy import engine_from_config +from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import configure_mappers -# import all models classes here for sqlalchemy mappers -# to pick up -from .mymodel import Page # flake8: noqa +import zope.sqlalchemy -# run configure mappers to ensure we avoid any race conditions +# import or define all models here to ensure they are attached to the +# Base.metadata prior to any initialization routines +from .page import Page # flake8: noqa +from .user import User # flake8: noqa + +# run configure_mappers after defining all of the models to ensure +# all relationships can be setup configure_mappers() + + +def get_engine(settings, prefix='sqlalchemy.'): + return engine_from_config(settings, prefix) + + +def get_session_factory(engine): + factory = sessionmaker() + factory.configure(bind=engine) + return factory + + +def get_tm_session(session_factory, transaction_manager): + """ + Get a ``sqlalchemy.orm.Session`` instance backed by a transaction. + + This function will hook the session to the transaction manager which + will take care of committing any changes. + + - When using pyramid_tm it will automatically be committed or aborted + depending on whether an exception is raised. + + - When using scripts you should wrap the session in a manager yourself. + For example:: + + import transaction + + engine = get_engine(settings) + session_factory = get_session_factory(engine) + with transaction.manager: + dbsession = get_tm_session(session_factory, transaction.manager) + + """ + dbsession = session_factory() + zope.sqlalchemy.register( + dbsession, transaction_manager=transaction_manager) + return dbsession + + +def includeme(config): + """ + Initialize the model for a Pyramid app. + + Activate this setup using ``config.include('tutorial.models')``. + + """ + settings = config.get_settings() + + # use pyramid_tm to hook the transaction lifecycle to the request + config.include('pyramid_tm') + + session_factory = get_session_factory(get_engine(settings)) + config.registry['dbsession_factory'] = session_factory + + # make request.dbsession available for use in Pyramid + config.add_request_method( + # r.tm is the transaction manager used by pyramid_tm + lambda r: get_tm_session(session_factory, r.tm), + 'dbsession', + reify=True + ) diff --git a/docs/tutorials/wiki2/src/models/tutorial/models/meta.py b/docs/tutorials/wiki2/src/models/tutorial/models/meta.py index 80ececd8c..fc3e8f1dd 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/models/meta.py +++ b/docs/tutorials/wiki2/src/models/tutorial/models/meta.py @@ -1,8 +1,5 @@ -from sqlalchemy import engine_from_config from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker from sqlalchemy.schema import MetaData -import zope.sqlalchemy # Recommended naming convention used by Alembic, as various different database # providers will autogenerate vastly different names making migrations more @@ -17,33 +14,3 @@ NAMING_CONVENTION = { metadata = MetaData(naming_convention=NAMING_CONVENTION) Base = declarative_base(metadata=metadata) - - -def includeme(config): - settings = config.get_settings() - dbmaker = get_dbmaker(get_engine(settings)) - - config.add_request_method( - lambda r: get_session(r.tm, dbmaker), - 'dbsession', - reify=True - ) - - config.include('pyramid_tm') - - -def get_session(transaction_manager, dbmaker): - dbsession = dbmaker() - zope.sqlalchemy.register(dbsession, - transaction_manager=transaction_manager) - return dbsession - - -def get_engine(settings, prefix='sqlalchemy.'): - return engine_from_config(settings, prefix) - - -def get_dbmaker(engine): - dbmaker = sessionmaker() - dbmaker.configure(bind=engine) - return dbmaker diff --git a/docs/tutorials/wiki2/src/models/tutorial/models/mymodel.py b/docs/tutorials/wiki2/src/models/tutorial/models/mymodel.py deleted file mode 100644 index 45571d78e..000000000 --- a/docs/tutorials/wiki2/src/models/tutorial/models/mymodel.py +++ /dev/null @@ -1,14 +0,0 @@ -from .meta import Base -from sqlalchemy import ( - Column, - Integer, - Text, -) - - -class Page(Base): - """ The SQLAlchemy declarative model class for a Page object. """ - __tablename__ = 'pages' - id = Column(Integer, primary_key=True) - name = Column(Text, unique=True) - data = Column(Integer) diff --git a/docs/tutorials/wiki2/src/models/tutorial/models/page.py b/docs/tutorials/wiki2/src/models/tutorial/models/page.py new file mode 100644 index 000000000..4dd5b5721 --- /dev/null +++ b/docs/tutorials/wiki2/src/models/tutorial/models/page.py @@ -0,0 +1,20 @@ +from sqlalchemy import ( + Column, + ForeignKey, + Integer, + Text, +) +from sqlalchemy.orm import relationship + +from .meta import Base + + +class Page(Base): + """ The SQLAlchemy declarative model class for a Page object. """ + __tablename__ = 'pages' + id = Column(Integer, primary_key=True) + name = Column(Text, nullable=False, unique=True) + data = Column(Integer, nullable=False) + + creator_id = Column(ForeignKey('users.id'), nullable=False) + creator = relationship('User', backref='created_pages') diff --git a/docs/tutorials/wiki2/src/models/tutorial/models/user.py b/docs/tutorials/wiki2/src/models/tutorial/models/user.py new file mode 100644 index 000000000..6fb32a1b2 --- /dev/null +++ b/docs/tutorials/wiki2/src/models/tutorial/models/user.py @@ -0,0 +1,29 @@ +import bcrypt +from sqlalchemy import ( + Column, + Integer, + Text, +) + +from .meta import Base + + +class User(Base): + """ The SQLAlchemy declarative model class for a User object. """ + __tablename__ = 'users' + id = Column(Integer, primary_key=True) + name = Column(Text, nullable=False, unique=True) + role = Column(Text, nullable=False) + + password_hash = Column(Text) + + def set_password(self, pw): + pwhash = bcrypt.hashpw(pw.encode('utf8'), bcrypt.gensalt()) + self.password_hash = pwhash + + def check_password(self, pw): + if self.password_hash is not None: + expected_hash = self.password_hash.encode('utf8') + actual_hash = bcrypt.hashpw(pw.encode('utf8'), expected_hash) + return expected_hash == actual_hash + return False diff --git a/docs/tutorials/wiki2/src/models/tutorial/routes.py b/docs/tutorials/wiki2/src/models/tutorial/routes.py new file mode 100644 index 000000000..25504ad4d --- /dev/null +++ b/docs/tutorials/wiki2/src/models/tutorial/routes.py @@ -0,0 +1,3 @@ +def includeme(config): + config.add_static_view('static', 'static', cache_max_age=3600) + config.add_route('home', '/') diff --git a/docs/tutorials/wiki2/src/models/tutorial/scripts/initializedb.py b/docs/tutorials/wiki2/src/models/tutorial/scripts/initializedb.py index 4aac4a848..f3c0a6fef 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/scripts/initializedb.py +++ b/docs/tutorials/wiki2/src/models/tutorial/scripts/initializedb.py @@ -7,13 +7,15 @@ from pyramid.paster import ( setup_logging, ) -from ..models.meta import ( - Base, - get_session, +from pyramid.scripts.common import parse_vars + +from ..models.meta import Base +from ..models import ( get_engine, - get_dbmaker, + get_session_factory, + get_tm_session, ) -from ..models.mymodel import Page +from ..models import Page, User def usage(argv): @@ -27,16 +29,29 @@ def main(argv=sys.argv): if len(argv) < 2: usage(argv) config_uri = argv[1] + options = parse_vars(argv[2:]) setup_logging(config_uri) - settings = get_appsettings(config_uri) + settings = get_appsettings(config_uri, options=options) engine = get_engine(settings) - dbmaker = get_dbmaker(engine) - - dbsession = get_session(transaction.manager, dbmaker) - Base.metadata.create_all(engine) + session_factory = get_session_factory(engine) + with transaction.manager: - model = Page(name='FrontPage', data='This is the front page') - dbsession.add(model) + dbsession = get_tm_session(session_factory, transaction.manager) + + editor = User(name='editor', role='editor') + editor.set_password('editor') + dbsession.add(editor) + + basic = User(name='basic', role='basic') + basic.set_password('basic') + dbsession.add(basic) + + page = Page( + name='FrontPage', + creator=editor, + data='This is the front page', + ) + dbsession.add(page) diff --git a/docs/tutorials/wiki2/src/models/tutorial/templates/404.jinja2 b/docs/tutorials/wiki2/src/models/tutorial/templates/404.jinja2 new file mode 100644 index 000000000..1917f83c7 --- /dev/null +++ b/docs/tutorials/wiki2/src/models/tutorial/templates/404.jinja2 @@ -0,0 +1,8 @@ +{% extends "layout.jinja2" %} + +{% block content %} +<div class="content"> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1> + <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p> +</div> +{% endblock content %} diff --git a/docs/tutorials/wiki2/src/models/tutorial/tests.py b/docs/tutorials/wiki2/src/models/tutorial/tests.py index b947e3bb1..c54945c28 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/tests.py +++ b/docs/tutorials/wiki2/src/models/tutorial/tests.py @@ -13,22 +13,22 @@ class BaseTest(unittest.TestCase): self.config = testing.setUp(settings={ 'sqlalchemy.url': 'sqlite:///:memory:' }) - self.config.include('.models.meta') + self.config.include('.models') settings = self.config.get_settings() - from .models.meta import ( - get_session, + from .models import ( get_engine, - get_dbmaker, + get_session_factory, + get_tm_session, ) self.engine = get_engine(settings) - dbmaker = get_dbmaker(self.engine) + session_factory = get_session_factory(self.engine) - self.session = get_session(transaction.manager, dbmaker) + self.session = get_tm_session(session_factory, transaction.manager) def init_database(self): - from .models.meta import Base + from .models import Base Base.metadata.create_all(self.engine) def tearDown(self): @@ -36,7 +36,7 @@ class BaseTest(unittest.TestCase): testing.tearDown() transaction.abort() - Base.metadata.create_all(self.engine) + Base.metadata.drop_all(self.engine) class TestMyViewSuccessCondition(BaseTest): @@ -45,7 +45,7 @@ class TestMyViewSuccessCondition(BaseTest): super(TestMyViewSuccessCondition, self).setUp() self.init_database() - from .models.mymodel import MyModel + from .models import MyModel model = MyModel(name='one', value=55) self.session.add(model) diff --git a/docs/tutorials/wiki2/src/models/tutorial/views/default.py b/docs/tutorials/wiki2/src/models/tutorial/views/default.py index 13ad8793c..ad0c728d7 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/views/default.py +++ b/docs/tutorials/wiki2/src/models/tutorial/views/default.py @@ -3,7 +3,7 @@ from pyramid.view import view_config from sqlalchemy.exc import DBAPIError -from ..models.mymodel import MyModel +from ..models import MyModel @view_config(route_name='home', renderer='../templates/mytemplate.jinja2') @@ -12,7 +12,7 @@ def my_view(request): query = request.dbsession.query(MyModel) one = query.filter(MyModel.name == 'one').first() except DBAPIError: - return Response(db_err_msg, content_type='text/plain', status_int=500) + return Response(db_err_msg, content_type='text/plain', status=500) return {'one': one, 'project': 'tutorial'} diff --git a/docs/tutorials/wiki2/src/models/tutorial/views/notfound.py b/docs/tutorials/wiki2/src/models/tutorial/views/notfound.py new file mode 100644 index 000000000..69d6e2804 --- /dev/null +++ b/docs/tutorials/wiki2/src/models/tutorial/views/notfound.py @@ -0,0 +1,7 @@ +from pyramid.view import notfound_view_config + + +@notfound_view_config(renderer='../templates/404.jinja2') +def notfound_view(request): + request.response.status = 404 + return {} |
