From 6a936654276b83ccd379c739e3c39d5a25457ab3 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 23 Jan 2016 05:04:45 -0800 Subject: Complete overhaul of Quick Tour - Databases and Forms --- docs/quick_tour.rst | 92 ++++++------ docs/quick_tour/sqla_demo/README.txt | 6 +- docs/quick_tour/sqla_demo/development.ini | 2 +- docs/quick_tour/sqla_demo/production.ini | 2 +- docs/quick_tour/sqla_demo/setup.cfg | 27 ++++ docs/quick_tour/sqla_demo/setup.py | 11 +- docs/quick_tour/sqla_demo/sqla_demo.sqlite | Bin 3072 -> 0 bytes docs/quick_tour/sqla_demo/sqla_demo/__init__.py | 1 + docs/quick_tour/sqla_demo/sqla_demo/models.py | 10 +- .../sqla_demo/sqla_demo/scripts/initializedb.py | 9 +- .../sqla_demo/sqla_demo/static/pyramid-16x16.png | Bin 0 -> 1319 bytes .../sqla_demo/sqla_demo/static/pyramid.png | Bin 33055 -> 12901 bytes .../sqla_demo/sqla_demo/static/theme.css | 154 +++++++++++++++++++++ .../sqla_demo/sqla_demo/static/theme.min.css | 1 + .../sqla_demo/sqla_demo/templates/mytemplate.pt | 131 ++++++++---------- docs/quick_tour/sqla_demo/sqla_demo/tests.py | 26 +++- docs/quick_tour/sqla_demo/sqla_demo/views.py | 5 +- 17 files changed, 338 insertions(+), 139 deletions(-) create mode 100644 docs/quick_tour/sqla_demo/setup.cfg delete mode 100644 docs/quick_tour/sqla_demo/sqla_demo.sqlite create mode 100644 docs/quick_tour/sqla_demo/sqla_demo/static/pyramid-16x16.png create mode 100644 docs/quick_tour/sqla_demo/sqla_demo/static/theme.css create mode 100644 docs/quick_tour/sqla_demo/sqla_demo/static/theme.min.css (limited to 'docs') diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst index 220fd4bca..a7c184776 100644 --- a/docs/quick_tour.rst +++ b/docs/quick_tour.rst @@ -812,17 +812,16 @@ We need to update our Jinja2 template to show counter increment in the session: :ref:`flash_messages`, :ref:`session_module`, and :term:`pyramid_redis_sessions`. + Databases ========= -Web applications mean data. Data means databases. Frequently SQL -databases. SQL databases frequently mean an "ORM" -(object-relational mapper.) In Python, ORM usually leads to the -mega-quality *SQLAlchemy*, a Python package that greatly eases working -with databases. +Web applications mean data. Data means databases. Frequently SQL databases. SQL +databases frequently mean an "ORM" (object-relational mapper.) In Python, ORM +usually leads to the mega-quality *SQLAlchemy*, a Python package that greatly +eases working with databases. -Pyramid and SQLAlchemy are great friends. That friendship includes a -scaffold! +Pyramid and SQLAlchemy are great friends. That friendship includes a scaffold! .. code-block:: bash @@ -830,50 +829,53 @@ scaffold! $ cd sqla_demo $ python setup.py develop -We now have a working sample SQLAlchemy application with all -dependencies installed. The sample project provides a console script to -initialize a SQLite database with tables. Let's run it and then start -the application: +We now have a working sample SQLAlchemy application with all dependencies +installed. The sample project provides a console script to initialize a SQLite +database with tables. Let's run it, then start the application: .. code-block:: bash $ initialize_sqla_demo_db development.ini $ pserve development.ini -The ORM eases the mapping of database structures into a programming -language. SQLAlchemy uses "models" for this mapping. The scaffold -generated a sample model: +The ORM eases the mapping of database structures into a programming language. +SQLAlchemy uses "models" for this mapping. The scaffold generated a sample +model: .. literalinclude:: quick_tour/sqla_demo/sqla_demo/models.py - :start-after: Start Sphinx Include - :end-before: End Sphinx Include + :language: python + :linenos: + :lineno-start: 21 + :lines: 21- -View code, which mediates the logic between web requests and the rest -of the system, can then easily get at the data thanks to SQLAlchemy: +View code, which mediates the logic between web requests and the rest of the +system, can then easily get at the data thanks to SQLAlchemy: .. literalinclude:: quick_tour/sqla_demo/sqla_demo/views.py - :start-after: Start Sphinx Include - :end-before: End Sphinx Include + :language: python + :linenos: + :lineno-start: 12 + :lines: 12-18 + :emphasize-lines: 4 .. seealso:: See also: - :ref:`Quick Tutorial Databases `, - `SQLAlchemy `_, - :ref:`making_a_console_script`, - :ref:`bfg_sql_wiki_tutorial`, and - :ref:`Application Transactions With pyramid_tm ` + :ref:`Quick Tutorial Databases `, `SQLAlchemy + `_, :ref:`making_a_console_script`, + :ref:`bfg_sql_wiki_tutorial`, and :ref:`Application Transactions with + pyramid_tm `. + Forms ===== -Developers have lots of opinions about web forms, and thus there are many -form libraries for Python. Pyramid doesn't directly bundle a form -library, but *Deform* is a popular choice for forms, -along with its related *Colander* schema system. +Developers have lots of opinions about web forms, thus there are many form +libraries for Python. Pyramid doesn't directly bundle a form library, but +*Deform* is a popular choice for forms, along with its related *Colander* +schema system. -As an example, imagine we want a form that edits a wiki page. The form -should have two fields on it, one of them a required title and the -other a rich text editor for the body. With Deform we can express this -as a Colander schema: +As an example, imagine we want a form that edits a wiki page. The form should +have two fields on it, one of them a required title and the other a rich text +editor for the body. With Deform we can express this as a Colander schema: .. code-block:: python @@ -884,8 +886,8 @@ as a Colander schema: widget=deform.widget.RichTextWidget() ) -With this in place, we can render the HTML for a form, -perhaps with form data from an existing page: +With this in place, we can render the HTML for a form, perhaps with form data +from an existing page: .. code-block:: python @@ -909,20 +911,18 @@ We'd like to handle form submission, validation, and saving: page['title'] = appstruct['title'] page['body'] = appstruct['body'] -Deform and Colander provide a very flexible combination for forms, -widgets, schemas, and validation. Recent versions of Deform also -include a :ref:`retail mode ` for gaining Deform -features on custom forms. +Deform and Colander provide a very flexible combination for forms, widgets, +schemas, and validation. Recent versions of Deform also include a :ref:`retail +mode ` for gaining Deform features on custom forms. -Also the ``deform_bootstrap`` Pyramid add-on restyles the stock Deform -widgets using attractive CSS from Twitter Bootstrap and more powerful widgets -from Chosen. +Also the ``deform_bootstrap`` Pyramid add-on restyles the stock Deform widgets +using attractive CSS from Twitter Bootstrap and more powerful widgets from +Chosen. .. seealso:: See also: - :ref:`Quick Tutorial Forms `, - :ref:`Deform `, - :ref:`Colander `, and - `deform_bootstrap `_ + :ref:`Quick Tutorial Forms `, :ref:`Deform `, + :ref:`Colander `, and `deform_bootstrap + `_. Conclusion ========== diff --git a/docs/quick_tour/sqla_demo/README.txt b/docs/quick_tour/sqla_demo/README.txt index f35d3aec5..c7f9d6474 100644 --- a/docs/quick_tour/sqla_demo/README.txt +++ b/docs/quick_tour/sqla_demo/README.txt @@ -6,9 +6,9 @@ Getting Started - cd -- $venv/bin/python setup.py develop +- $VENV/bin/python setup.py develop -- $venv/bin/initialize_sqla_demo_db development.ini +- $VENV/bin/initialize_sqla_demo_db development.ini -- $venv/bin/pserve development.ini +- $VENV/bin/pserve development.ini diff --git a/docs/quick_tour/sqla_demo/development.ini b/docs/quick_tour/sqla_demo/development.ini index 174468abf..cdf20638e 100644 --- a/docs/quick_tour/sqla_demo/development.ini +++ b/docs/quick_tour/sqla_demo/development.ini @@ -68,4 +68,4 @@ level = NOTSET formatter = generic [formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s +format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s diff --git a/docs/quick_tour/sqla_demo/production.ini b/docs/quick_tour/sqla_demo/production.ini index dc0ba304f..38f3b6318 100644 --- a/docs/quick_tour/sqla_demo/production.ini +++ b/docs/quick_tour/sqla_demo/production.ini @@ -59,4 +59,4 @@ level = NOTSET formatter = generic [formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s +format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s diff --git a/docs/quick_tour/sqla_demo/setup.cfg b/docs/quick_tour/sqla_demo/setup.cfg new file mode 100644 index 000000000..9f91cd122 --- /dev/null +++ b/docs/quick_tour/sqla_demo/setup.cfg @@ -0,0 +1,27 @@ +[nosetests] +match=^test +nocapture=1 +cover-package=sqla_demo +with-coverage=1 +cover-erase=1 + +[compile_catalog] +directory = sqla_demo/locale +domain = sqla_demo +statistics = true + +[extract_messages] +add_comments = TRANSLATORS: +output_file = sqla_demo/locale/sqla_demo.pot +width = 80 + +[init_catalog] +domain = sqla_demo +input_file = sqla_demo/locale/sqla_demo.pot +output_dir = sqla_demo/locale + +[update_catalog] +domain = sqla_demo +input_file = sqla_demo/locale/sqla_demo.pot +output_dir = sqla_demo/locale +previous = true diff --git a/docs/quick_tour/sqla_demo/setup.py b/docs/quick_tour/sqla_demo/setup.py index ac2eed035..a9a8842e2 100644 --- a/docs/quick_tour/sqla_demo/setup.py +++ b/docs/quick_tour/sqla_demo/setup.py @@ -3,15 +3,18 @@ import os from setuptools import setup, find_packages here = os.path.abspath(os.path.dirname(__file__)) -README = open(os.path.join(here, 'README.txt')).read() -CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() +with open(os.path.join(here, 'README.txt')) as f: + README = f.read() +with open(os.path.join(here, 'CHANGES.txt')) as f: + CHANGES = f.read() requires = [ 'pyramid', + 'pyramid_chameleon', + 'pyramid_debugtoolbar', + 'pyramid_tm', 'SQLAlchemy', 'transaction', - 'pyramid_tm', - 'pyramid_debugtoolbar', 'zope.sqlalchemy', 'waitress', ] diff --git a/docs/quick_tour/sqla_demo/sqla_demo.sqlite b/docs/quick_tour/sqla_demo/sqla_demo.sqlite deleted file mode 100644 index fa6adb104..000000000 Binary files a/docs/quick_tour/sqla_demo/sqla_demo.sqlite and /dev/null differ diff --git a/docs/quick_tour/sqla_demo/sqla_demo/__init__.py b/docs/quick_tour/sqla_demo/sqla_demo/__init__.py index aac7c5e69..867049e4f 100644 --- a/docs/quick_tour/sqla_demo/sqla_demo/__init__.py +++ b/docs/quick_tour/sqla_demo/sqla_demo/__init__.py @@ -14,6 +14,7 @@ def main(global_config, **settings): DBSession.configure(bind=engine) Base.metadata.bind = engine config = Configurator(settings=settings) + config.include('pyramid_chameleon') config.add_static_view('static', 'static', cache_max_age=3600) config.add_route('home', '/') config.scan() diff --git a/docs/quick_tour/sqla_demo/sqla_demo/models.py b/docs/quick_tour/sqla_demo/sqla_demo/models.py index 3dfb40e58..a0d3e7b71 100644 --- a/docs/quick_tour/sqla_demo/sqla_demo/models.py +++ b/docs/quick_tour/sqla_demo/sqla_demo/models.py @@ -1,5 +1,6 @@ from sqlalchemy import ( Column, + Index, Integer, Text, ) @@ -16,14 +17,11 @@ from zope.sqlalchemy import ZopeTransactionExtension DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension())) Base = declarative_base() -# Start Sphinx Include + class MyModel(Base): __tablename__ = 'models' id = Column(Integer, primary_key=True) - name = Column(Text, unique=True) + name = Column(Text) value = Column(Integer) - def __init__(self, name, value): - self.name = name - self.value = value - # End Sphinx Include +Index('my_index', MyModel.name, unique=True, mysql_length=255) diff --git a/docs/quick_tour/sqla_demo/sqla_demo/scripts/initializedb.py b/docs/quick_tour/sqla_demo/sqla_demo/scripts/initializedb.py index 66feb3008..7dfdece15 100644 --- a/docs/quick_tour/sqla_demo/sqla_demo/scripts/initializedb.py +++ b/docs/quick_tour/sqla_demo/sqla_demo/scripts/initializedb.py @@ -9,6 +9,8 @@ from pyramid.paster import ( setup_logging, ) +from pyramid.scripts.common import parse_vars + from ..models import ( DBSession, MyModel, @@ -18,17 +20,18 @@ from ..models import ( def usage(argv): cmd = os.path.basename(argv[0]) - print('usage: %s \n' + print('usage: %s [var=value]\n' '(example: "%s development.ini")' % (cmd, cmd)) sys.exit(1) def main(argv=sys.argv): - if len(argv) != 2: + 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 = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.create_all(engine) diff --git a/docs/quick_tour/sqla_demo/sqla_demo/static/pyramid-16x16.png b/docs/quick_tour/sqla_demo/sqla_demo/static/pyramid-16x16.png new file mode 100644 index 000000000..979203112 Binary files /dev/null and b/docs/quick_tour/sqla_demo/sqla_demo/static/pyramid-16x16.png differ diff --git a/docs/quick_tour/sqla_demo/sqla_demo/static/pyramid.png b/docs/quick_tour/sqla_demo/sqla_demo/static/pyramid.png index 347e05549..4ab837be9 100644 Binary files a/docs/quick_tour/sqla_demo/sqla_demo/static/pyramid.png and b/docs/quick_tour/sqla_demo/sqla_demo/static/pyramid.png differ diff --git a/docs/quick_tour/sqla_demo/sqla_demo/static/theme.css b/docs/quick_tour/sqla_demo/sqla_demo/static/theme.css new file mode 100644 index 000000000..0f4b1a4d4 --- /dev/null +++ b/docs/quick_tour/sqla_demo/sqla_demo/static/theme.css @@ -0,0 +1,154 @@ +@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700); +body { + font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: 300; + color: #ffffff; + background: #bc2131; +} +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: 300; +} +p { + font-weight: 300; +} +.font-normal { + font-weight: 400; +} +.font-semi-bold { + font-weight: 600; +} +.font-bold { + font-weight: 700; +} +.starter-template { + margin-top: 250px; +} +.starter-template .content { + margin-left: 10px; +} +.starter-template .content h1 { + margin-top: 10px; + font-size: 60px; +} +.starter-template .content h1 .smaller { + font-size: 40px; + color: #f2b7bd; +} +.starter-template .content .lead { + font-size: 25px; + color: #f2b7bd; +} +.starter-template .content .lead .font-normal { + color: #ffffff; +} +.starter-template .links { + float: right; + right: 0; + margin-top: 125px; +} +.starter-template .links ul { + display: block; + padding: 0; + margin: 0; +} +.starter-template .links ul li { + list-style: none; + display: inline; + margin: 0 10px; +} +.starter-template .links ul li:first-child { + margin-left: 0; +} +.starter-template .links ul li:last-child { + margin-right: 0; +} +.starter-template .links ul li.current-version { + color: #f2b7bd; + font-weight: 400; +} +.starter-template .links ul li a, a { + color: #f2b7bd; + text-decoration: underline; +} +.starter-template .links ul li a:hover, a:hover { + color: #ffffff; + text-decoration: underline; +} +.starter-template .links ul li .icon-muted { + color: #eb8b95; + margin-right: 5px; +} +.starter-template .links ul li:hover .icon-muted { + color: #ffffff; +} +.starter-template .copyright { + margin-top: 10px; + font-size: 0.9em; + color: #f2b7bd; + text-transform: lowercase; + float: right; + right: 0; +} +@media (max-width: 1199px) { + .starter-template .content h1 { + font-size: 45px; + } + .starter-template .content h1 .smaller { + font-size: 30px; + } + .starter-template .content .lead { + font-size: 20px; + } +} +@media (max-width: 991px) { + .starter-template { + margin-top: 0; + } + .starter-template .logo { + margin: 40px auto; + } + .starter-template .content { + margin-left: 0; + text-align: center; + } + .starter-template .content h1 { + margin-bottom: 20px; + } + .starter-template .links { + float: none; + text-align: center; + margin-top: 60px; + } + .starter-template .copyright { + float: none; + text-align: center; + } +} +@media (max-width: 767px) { + .starter-template .content h1 .smaller { + font-size: 25px; + display: block; + } + .starter-template .content .lead { + font-size: 16px; + } + .starter-template .links { + margin-top: 40px; + } + .starter-template .links ul li { + display: block; + margin: 0; + } + .starter-template .links ul li .icon-muted { + display: none; + } + .starter-template .copyright { + margin-top: 20px; + } +} diff --git a/docs/quick_tour/sqla_demo/sqla_demo/static/theme.min.css b/docs/quick_tour/sqla_demo/sqla_demo/static/theme.min.css new file mode 100644 index 000000000..0d25de5b6 --- /dev/null +++ b/docs/quick_tour/sqla_demo/sqla_demo/static/theme.min.css @@ -0,0 +1 @@ +@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a,a{color:#f2b7bd;text-decoration:underline}.starter-template .links ul li a:hover,a:hover{color:#fff;text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}} diff --git a/docs/quick_tour/sqla_demo/sqla_demo/templates/mytemplate.pt b/docs/quick_tour/sqla_demo/sqla_demo/templates/mytemplate.pt index 321c0f5fb..99df4a8b7 100644 --- a/docs/quick_tour/sqla_demo/sqla_demo/templates/mytemplate.pt +++ b/docs/quick_tour/sqla_demo/sqla_demo/templates/mytemplate.pt @@ -1,76 +1,67 @@ - - - - The Pyramid Web Framework - - - - - - - - - - -
-
-
-
pyramid
-
-
-
-
-

- Welcome to ${project}, an application generated by
- the Pyramid web framework. -

-
-
-
-
-
-

Search documentation

-
- - -
+ + + + + + + + + + + Alchemy Scaffold for The Pyramid Web Framework + + + + + + + + + + + + + +
+
+
+
+ +
+
+
+

Pyramid Alchemy scaffold

+

Welcome to ${project}, an application generated by
the Pyramid Web Framework 1.6.

+
+
-
-
- - + + + + + + + diff --git a/docs/quick_tour/sqla_demo/sqla_demo/tests.py b/docs/quick_tour/sqla_demo/sqla_demo/tests.py index 6fef6d695..be288d580 100644 --- a/docs/quick_tour/sqla_demo/sqla_demo/tests.py +++ b/docs/quick_tour/sqla_demo/sqla_demo/tests.py @@ -6,7 +6,7 @@ from pyramid import testing from .models import DBSession -class TestMyView(unittest.TestCase): +class TestMyViewSuccessCondition(unittest.TestCase): def setUp(self): self.config = testing.setUp() from sqlalchemy import create_engine @@ -25,9 +25,31 @@ class TestMyView(unittest.TestCase): DBSession.remove() testing.tearDown() - def test_it(self): + def test_passing_view(self): from .views import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['one'].name, 'one') self.assertEqual(info['project'], 'sqla_demo') + + +class TestMyViewFailureCondition(unittest.TestCase): + def setUp(self): + self.config = testing.setUp() + from sqlalchemy import create_engine + engine = create_engine('sqlite://') + from .models import ( + Base, + MyModel, + ) + DBSession.configure(bind=engine) + + def tearDown(self): + DBSession.remove() + testing.tearDown() + + def test_failing_view(self): + from .views import my_view + request = testing.DummyRequest() + info = my_view(request) + self.assertEqual(info.status_int, 500) \ No newline at end of file diff --git a/docs/quick_tour/sqla_demo/sqla_demo/views.py b/docs/quick_tour/sqla_demo/sqla_demo/views.py index 768a7e42e..964f76441 100644 --- a/docs/quick_tour/sqla_demo/sqla_demo/views.py +++ b/docs/quick_tour/sqla_demo/sqla_demo/views.py @@ -12,19 +12,18 @@ from .models import ( @view_config(route_name='home', renderer='templates/mytemplate.pt') def my_view(request): try: - # Start Sphinx Include one = DBSession.query(MyModel).filter(MyModel.name == 'one').first() - # End Sphinx Include except DBAPIError: return Response(conn_err_msg, content_type='text/plain', status_int=500) return {'one': one, 'project': 'sqla_demo'} + conn_err_msg = """\ Pyramid is having a problem using your SQL database. The problem might be caused by one of the following things: 1. You may need to run the "initialize_sqla_demo_db" script - to initialize your database tables. Check your virtual + to initialize your database tables. Check your virtual environment's "bin" directory for this script and try to run it. 2. Your database server may not be running. Check that the -- cgit v1.2.3