[Checkins] SVN: grokapps/gbe99bottles/ Initial import
Michael Haubenwallner
michael at d2m.at
Sat Aug 16 03:21:59 EDT 2008
Log message for revision 89905:
Initial import
Changed:
A grokapps/gbe99bottles/
A grokapps/gbe99bottles/README.txt
A grokapps/gbe99bottles/REVIEW.txt
A grokapps/gbe99bottles/bootstrap.py
A grokapps/gbe99bottles/buildout.cfg
A grokapps/gbe99bottles/setup.py
A grokapps/gbe99bottles/src/
A grokapps/gbe99bottles/src/gbe99bottles/
A grokapps/gbe99bottles/src/gbe99bottles/__init__.py
A grokapps/gbe99bottles/src/gbe99bottles/app.py
A grokapps/gbe99bottles/src/gbe99bottles/app.txt
A grokapps/gbe99bottles/src/gbe99bottles/configure.zcml
A grokapps/gbe99bottles/src/gbe99bottles/ftesting.zcml
A grokapps/gbe99bottles/src/gbe99bottles/tests.py
-=-
Added: grokapps/gbe99bottles/README.txt
===================================================================
--- grokapps/gbe99bottles/README.txt (rev 0)
+++ grokapps/gbe99bottles/README.txt 2008-08-16 07:21:58 UTC (rev 89905)
@@ -0,0 +1,71 @@
+Grok-by-Example: 99 Bottles Song
+================================
+
+:Author: d2m (michael at d2m.at)
+:Motivation: look at the original source and the Grok code side-by-side
+ and deduce from both
+
+A basic Grok 'Hello world' application [source__], cf implementations in other
+languages at the '99 Bootles of Beer' website, eg. the Python version [source__].
+
+__ http://svn.zope.org/grokapps/gbe99bottles/src/gbe99bottles/app.py?view=markup
+__ http://99-bottles-of-beer.net/language-python-808.html
+
+
+Overview
+--------
+
+The '99 Bottles of Beer' website holds a collection of the Song 99 Bottles of
+Beer programmed in different programming languages. Actually the song is
+represented in more than 1200 different programming languages and variations.
+
+The Grok implementation uses persistent 'wall' and 'bottle' objects. It follows
+the song lyrics by putting bottles on the wall, taking them off again and at
+each step reporting the state of the scenery.
+
+Usage
+-----
+
+This example is a complete Grok app on its own. Here is how to use it::
+
+ # checkout the example to your harddisk
+ svn co svn://svn.zope.org/repos/main/grokapps/gbe99bottles
+
+ # change to the newly created directory
+ cd gbe99bottles
+
+ # make it a virtualenv
+ virtualenv --no-site-packages .
+
+ # activate the virtualenv
+ source bin/activate
+
+ # bootstrap the buildout environment
+ bin/python bootstrap.py
+
+ # run the buildout
+ bin/buildout
+
+ # test the example app
+ bin/test
+
+ # run the example app
+ bin/zopectl fg
+
+ # point your browser to
+ http://localhost:8080
+
+ # login
+ username: grok
+ password: grok
+
+ # create an instance of the registered grok app
+ # and use it
+
+That's it!
+
+Need help? There is the Grok Users mailinglist at grok-dev at zope.org
+(http://mail.zope.org/mailman/listinfo/grok-dev),
+the Grok IRC channel at irc.freenode.net/#grok
+and the Grok website at http://grok.zope.org
+
Added: grokapps/gbe99bottles/REVIEW.txt
===================================================================
--- grokapps/gbe99bottles/REVIEW.txt (rev 0)
+++ grokapps/gbe99bottles/REVIEW.txt 2008-08-16 07:21:58 UTC (rev 89905)
@@ -0,0 +1,126 @@
+Grok-by-Example: 99 Bottles Song
+================================
+
+:Author: d2m (michael at d2m.at)
+
+A basic Grok 'Hello world' application [source__], cf implementations in other
+languages at the '99 Bootles of Beer' website, eg. the Python version [source__].
+
+__ http://svn.zope.org/grokapps/gbe99bottles/src/gbe99bottles/app.py?view=markup
+__ http://99-bottles-of-beer.net/language-python-808.html
+
+
+Overview
+========
+
+The '99 Bottles of Beer' website holds a collection of the Song 99 Bottles of
+Beer programmed in different programming languages. Actually the song is
+represented in more than 1200 different programming languages and variations.
+
+The Grok implementation uses persistent 'Wall' and 'Bottle' objects. It follows
+the song lyrics by putting bottles on the wall, taking them off again and - at
+each step - reporting the state of the scenery.
+
+Review
+~~~~~~
+
+This example might look like a stupid exercise to you, but it shows a few fine
+features of the Grok framework:
+
+Application initialization
+--------------------------
+
+When we create the 'Song' Application object, we want to set it to an initial
+state, like creating a Wall, a number of Bootles and add them to the Wall.
+
+Grok also is an event driven framework. Events can be sent and subscribed to.
+When the Application object is created it is added to the object graph an
+'ObjectAddedEvent' is sent by the framework. We can now simply *subscribe* to
+that event (or the Interface that describes the event type) and initialize our
+application objects by adding a container and filling it with objects::
+
+ @grok.subscribe(Song, grok.IObjectAddedEvent)
+ def handle(obj, event):
+ obj['wall']=wall=Wall()
+ wall.add_99_bottles()
+
+More on events can be found at the `Grok website`__.
+
+__ http://grok.zope.org/doc/current/grok_overview.html#events
+
+Request
+-------
+
+The application accepts only one request: the default view.
+
+Grok uses 'index' for its default view name (lowercase of the class name).
+This view class is bound to the 'Song' application object::
+
+ class Index(grok.View):
+ grok.context(Song)
+
+Response
+--------
+
+The view classes are created and called for each Request. The 'update' and
+'render' methods are essential parts of rendering a Response from a view class.
+
+'update' - if existing - is always called first. It allows us to collect request
+parameters and bind them to properties or - in the example - create a shortcut
+to the Wall object::
+
+ def update(self):
+ self.wall=self.context['wall']
+
+'render' is called last, its output is returned to the user.
+
+Models
+------
+
+Grok content objects are subclassed from grok.Model or grok.Container.
+
+ class Bottle(grok.Model):
+ pass
+
+ class Wall(grok.Container):
+ ...
+
+The application object itself is subclassed from grok.Application and
+grok.Container.
+
+ class Song(grok.Application,grok.Container):
+ pass
+
+Persistent Storage
+------------------
+
+When a Response is created by the view class Bottle objects are removed from and
+added to the Wall container by calling methods of the Wall instance object::
+
+ def remove_a_bottle(self):
+ bottles=list(self.keys())
+ del self[bottles[-1]]
+
+ def add_99_bottles(self):
+ for n in range(1,100):
+ self[str(n)]=Bottle()
+
+Searching
+---------
+
+The objects can simply be queried using python::
+
+ def contents(self):
+ return len(self.items())
+
+Testing
+-------
+
+A functional doctest ('app.txt') is added to ensure correct operations.
+
+Overall
+-------
+
+It was fun and easy to implement the '99 Bottles' Song using persistent objects.
+Functional testing made development even simpler.
+
Added: grokapps/gbe99bottles/bootstrap.py
===================================================================
--- grokapps/gbe99bottles/bootstrap.py (rev 0)
+++ grokapps/gbe99bottles/bootstrap.py 2008-08-16 07:21:58 UTC (rev 89905)
@@ -0,0 +1,66 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+
+$Id: bootstrap.py 85041 2008-03-31 15:57:30Z andreasjung $
+"""
+
+import os, shutil, sys, tempfile, urllib2
+
+tmpeggs = tempfile.mkdtemp()
+
+try:
+ import pkg_resources
+except ImportError:
+ ez = {}
+ exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
+ ).read() in ez
+ ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
+
+ import pkg_resources
+
+if sys.platform == 'win32':
+ def quote(c):
+ if ' ' in c:
+ return '"%s"' % c # work around spawn lamosity on windows
+ else:
+ return c
+else:
+ def quote (c):
+ return c
+
+cmd = 'from setuptools.command.easy_install import main; main()'
+ws = pkg_resources.working_set
+assert os.spawnle(
+ os.P_WAIT, sys.executable, quote (sys.executable),
+ '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout',
+ dict(os.environ,
+ PYTHONPATH=
+ ws.find(pkg_resources.Requirement.parse('setuptools')).location
+ ),
+ ) == 0
+
+ws.add_entry(tmpeggs)
+ws.require('zc.buildout')
+import zc.buildout.buildout
+zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
+shutil.rmtree(tmpeggs)
+
+# grokproject specific addition to standard bootstrap.py:
+# Install eggbasket too.
+zc.buildout.buildout.main(sys.argv[1:] + ['install', 'eggbasket'])
Added: grokapps/gbe99bottles/buildout.cfg
===================================================================
--- grokapps/gbe99bottles/buildout.cfg (rev 0)
+++ grokapps/gbe99bottles/buildout.cfg 2008-08-16 07:21:58 UTC (rev 89905)
@@ -0,0 +1,74 @@
+[buildout]
+develop = .
+parts = eggbasket app data zopectl i18n test
+newest = false
+extends = http://grok.zope.org/releaseinfo/grok-0.13.cfg
+# eggs will be installed in the default buildout location
+# (see .buildout/default.cfg in your home directory)
+# unless you specify an eggs-directory option here.
+
+versions = versions
+
+[app]
+recipe = zc.zope3recipes>=0.5.3:application
+eggs = gbe99bottles
+site.zcml = <include package="gbe99bottles" />
+ <include package="zope.app.twisted" />
+
+ <configure i18n_domain="gbe99bottles">
+ <unauthenticatedPrincipal id="zope.anybody"
+ title="Unauthenticated User" />
+ <unauthenticatedGroup id="zope.Anybody"
+ title="Unauthenticated Users" />
+ <authenticatedGroup id="zope.Authenticated"
+ title="Authenticated Users" />
+ <everybodyGroup id="zope.Everybody"
+ title="All Users" />
+ <principal id="zope.manager"
+ title="Manager"
+ login="grok"
+ password_manager="Plain Text"
+ password="grok"
+ />
+
+ <!-- Replace the following directive if you do not want
+ public access -->
+ <grant permission="zope.View"
+ principal="zope.Anybody" />
+ <grant permission="zope.app.dublincore.view"
+ principal="zope.Anybody" />
+
+ <role id="zope.Manager" title="Site Manager" />
+ <role id="zope.Member" title="Site Member" />
+ <grantAll role="zope.Manager" />
+ <grant role="zope.Manager"
+ principal="zope.manager" />
+ </configure>
+
+[data]
+recipe = zc.recipe.filestorage
+
+# this section named so that the start/stop script is called bin/zopectl
+[zopectl]
+recipe = zc.zope3recipes:instance
+application = app
+zope.conf = ${data:zconfig}
+ devmode on
+
+[test]
+recipe = zc.recipe.testrunner
+eggs = gbe99bottles
+defaults = ['--tests-pattern', '^f?tests$', '-v']
+
+# this section named so that the i18n scripts are called bin/i18n...
+[i18n]
+recipe = lovely.recipe:i18n
+package = gbe99bottles
+domain = gbe99bottles
+location = src/gbe99bottles
+output = locales
+
+[eggbasket]
+recipe = z3c.recipe.eggbasket
+eggs = grok
+url = http://grok.zope.org/releaseinfo/grok-eggs-0.13.tgz
Added: grokapps/gbe99bottles/setup.py
===================================================================
--- grokapps/gbe99bottles/setup.py (rev 0)
+++ grokapps/gbe99bottles/setup.py 2008-08-16 07:21:58 UTC (rev 89905)
@@ -0,0 +1,34 @@
+from setuptools import setup, find_packages
+
+version = '0.1'
+
+setup(name='gbe99bottles',
+ version=version,
+ description="Grok-by-Example: 99 Bottles App",
+ long_description="""\
+ created for http://99-bottles-of-beer.net (language: Grok)
+""",
+ # Get strings from http://www.python.org/pypi?%3Aaction=list_classifiers
+ classifiers=['Development Status :: 4 - Beta',
+ 'Framework :: Zope3',
+ 'License :: OSI Approved :: Zope Public License',
+ 'Programming Language :: Python',
+ 'Programming Language :: Zope',],
+ keywords="Grok Example",
+ author="d2m",
+ author_email="michael at d2m.at",
+ url="http://blog.d2m.at",
+ license="",
+ package_dir={'': 'src'},
+ packages=find_packages('src'),
+ include_package_data=True,
+ zip_safe=False,
+ install_requires=['setuptools',
+ 'grok',
+ 'z3c.testsetup',
+ # Add extra requirements here
+ ],
+ entry_points="""
+ # Add entry points here
+ """,
+ )
Added: grokapps/gbe99bottles/src/gbe99bottles/__init__.py
===================================================================
--- grokapps/gbe99bottles/src/gbe99bottles/__init__.py (rev 0)
+++ grokapps/gbe99bottles/src/gbe99bottles/__init__.py 2008-08-16 07:21:58 UTC (rev 89905)
@@ -0,0 +1 @@
+# this directory is a package
Added: grokapps/gbe99bottles/src/gbe99bottles/app.py
===================================================================
--- grokapps/gbe99bottles/src/gbe99bottles/app.py (rev 0)
+++ grokapps/gbe99bottles/src/gbe99bottles/app.py 2008-08-16 07:21:58 UTC (rev 89905)
@@ -0,0 +1,68 @@
+import grok
+
+class Song(grok.Application,grok.Container):
+ pass
+
+class Bottle(grok.Model):
+ pass
+
+class Wall(grok.Container):
+
+ def add_99_bottles(self):
+ for n in range(1,100):
+ self[str(n)]=Bottle()
+
+ def remove_a_bottle(self):
+ bottles=list(self.keys())
+ del self[bottles[-1]]
+
+ def describe_me(self):
+ num_bottles=self.contents()
+ if num_bottles == 0:
+ text='No more bottles of beer'
+ elif num_bottles == 1:
+ text='1 bottle of beer'
+ else:
+ text='%d bottles of beer' % num_bottles
+ return text
+
+ def contents(self):
+ return len(self.items())
+
+class Index(grok.View):
+ grok.context(Song)
+
+ def still_beer(self):
+ return self.wall.contents() > 0
+
+ def take_one_down(self):
+ self.wall.remove_a_bottle()
+
+ def buy_some_more(self):
+ self.wall.add_99_bottles()
+
+ def describe_wall(self):
+ return self.wall.describe_me()
+
+ def update(self):
+ self.wall=self.context['wall']
+
+ def render(self):
+ out=[]
+ while self.still_beer():
+ description=self.describe_wall()
+ out.append('%s on the wall, %s.' % (description, description.lower()))
+ self.take_one_down()
+ description=self.describe_wall()
+ out.append('Take one down and pass it around, %s on the wall.\n' % description.lower())
+ description=self.describe_wall()
+ out.append('%s on the wall, %s.' % (description, description.lower()))
+ self.buy_some_more()
+ description=self.describe_wall()
+ out.append('Go to the store and buy some more, %s on the wall.' % description.lower())
+ return '\n'.join(out)
+
+ at grok.subscribe(Song, grok.IObjectAddedEvent)
+def handle(obj, event):
+ obj['wall']=wall=Wall()
+ wall.add_99_bottles()
Added: grokapps/gbe99bottles/src/gbe99bottles/app.txt
===================================================================
--- grokapps/gbe99bottles/src/gbe99bottles/app.txt (rev 0)
+++ grokapps/gbe99bottles/src/gbe99bottles/app.txt 2008-08-16 07:21:58 UTC (rev 89905)
@@ -0,0 +1,43 @@
+Do a functional doctest test on the app.
+========================================
+
+:Test-Layer: functional
+
+Let's first create an instance of the Wall at the top level:
+
+ >>> from gbe99bottles.app import Wall
+ >>> root = getRootFolder()
+ >>> root['wall'] = Wall()
+
+Creating the wall implicitely puts 99 beers on it.
+
+ >>> root['wall'].contents()
+ 99
+
+Create a Browser and visit the instance you just created:
+
+ >>> from zope.testbrowser.testing import Browser
+ >>> browser = Browser()
+ >>> browser.open('http://localhost/wall')
+
+Sing the '99 Bottles of Beer' Song (original lyrics at
+http://99-bottles-of-beer.net/lyrics.html):
+
+ >>> print browser.contents
+ 99 bottles of beer on the wall, 99 bottles of beer.
+ Take one down and pass it around, 98 bottles of beer on the wall.
+ ...
+ 2 bottles of beer on the wall, 2 bottles of beer.
+ Take one down and pass it around, 1 bottle of beer on the wall.
+ <BLANKLINE>
+ 1 bottle of beer on the wall, 1 bottle of beer.
+ Take one down and pass it around, no more bottles of beer on the wall.
+ <BLANKLINE>
+ No more bottles of beer on the wall, no more bottles of beer.
+ Go to the store and buy some more, 99 bottles of beer on the wall.
+
+Again check the number of beers on the wall.
+
+ >>> root['wall'].contents()
+ 99
+
\ No newline at end of file
Added: grokapps/gbe99bottles/src/gbe99bottles/configure.zcml
===================================================================
--- grokapps/gbe99bottles/src/gbe99bottles/configure.zcml (rev 0)
+++ grokapps/gbe99bottles/src/gbe99bottles/configure.zcml 2008-08-16 07:21:58 UTC (rev 89905)
@@ -0,0 +1,6 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+ xmlns:grok="http://namespaces.zope.org/grok">
+ <include package="grok" />
+ <includeDependencies package="." />
+ <grok:grok package="." />
+</configure>
Added: grokapps/gbe99bottles/src/gbe99bottles/ftesting.zcml
===================================================================
--- grokapps/gbe99bottles/src/gbe99bottles/ftesting.zcml (rev 0)
+++ grokapps/gbe99bottles/src/gbe99bottles/ftesting.zcml 2008-08-16 07:21:58 UTC (rev 89905)
@@ -0,0 +1,35 @@
+<configure
+ xmlns="http://namespaces.zope.org/zope"
+ i18n_domain="gbe99bottles"
+ package="gbe99bottles"
+ >
+
+ <include package="grok" />
+ <include package="gbe99bottles" />
+
+ <!-- Typical functional testing security setup -->
+ <securityPolicy
+ component="zope.securitypolicy.zopepolicy.ZopeSecurityPolicy"
+ />
+
+ <unauthenticatedPrincipal
+ id="zope.anybody"
+ title="Unauthenticated User"
+ />
+ <grant
+ permission="zope.View"
+ principal="zope.anybody"
+ />
+
+ <principal
+ id="zope.mgr"
+ title="Manager"
+ login="mgr"
+ password="mgrpw"
+ />
+
+ <role id="zope.Manager" title="Site Manager" />
+ <grantAll role="zope.Manager" />
+ <grant role="zope.Manager" principal="zope.mgr" />
+
+</configure>
Added: grokapps/gbe99bottles/src/gbe99bottles/tests.py
===================================================================
--- grokapps/gbe99bottles/src/gbe99bottles/tests.py (rev 0)
+++ grokapps/gbe99bottles/src/gbe99bottles/tests.py 2008-08-16 07:21:58 UTC (rev 89905)
@@ -0,0 +1,12 @@
+import os.path
+import z3c.testsetup
+import gbe99bottles
+from zope.app.testing.functional import ZCMLLayer
+
+
+ftesting_zcml = os.path.join(
+ os.path.dirname(gbe99bottles.__file__), 'ftesting.zcml')
+FunctionalLayer = ZCMLLayer(ftesting_zcml, __name__, 'FunctionalLayer',
+ allow_teardown=True)
+
+test_suite = z3c.testsetup.register_all_tests('gbe99bottles')
More information about the Checkins
mailing list