[Checkins] SVN: grokapps/gbeguestbook/ Initial checkin
Michael Haubenwallner
michael at d2m.at
Fri Aug 15 08:41:00 EDT 2008
Log message for revision 89878:
Initial checkin
Changed:
A grokapps/gbeguestbook/
A grokapps/gbeguestbook/README.txt
A grokapps/gbeguestbook/REVIEW.txt
A grokapps/gbeguestbook/bootstrap.py
A grokapps/gbeguestbook/buildout.cfg
A grokapps/gbeguestbook/setup.py
A grokapps/gbeguestbook/src/
A grokapps/gbeguestbook/src/gbeguestbook/
A grokapps/gbeguestbook/src/gbeguestbook/__init__.py
A grokapps/gbeguestbook/src/gbeguestbook/app.py
A grokapps/gbeguestbook/src/gbeguestbook/app.txt
A grokapps/gbeguestbook/src/gbeguestbook/configure.zcml
A grokapps/gbeguestbook/src/gbeguestbook/ftesting.zcml
A grokapps/gbeguestbook/src/gbeguestbook/tests.py
-=-
Added: grokapps/gbeguestbook/README.txt
===================================================================
--- grokapps/gbeguestbook/README.txt (rev 0)
+++ grokapps/gbeguestbook/README.txt 2008-08-15 12:40:59 UTC (rev 89878)
@@ -0,0 +1,68 @@
+Grok-by-Example: Guestbook
+==========================
+
+: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 Guestbook' application [source__] ported from the
+Google Appengine [GAE] demo application [source__].
+
+__ http://svn.zope.org/grokapps/gbeguestbook/src/gbeguestbook/app.py?view=markup
+__ http://code.google.com/p/googleappengine/source/browse/trunk/demos/guestbook/guestbook.py
+
+
+Overview
+--------
+
+The application presents you a list of 10 guestbook entries both by
+authenticated and anonymous users, reverse sorted by date of creation
+and a form to submit new entries to the guestbook. Response text
+formatting is done using python string templates only.
+
+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/gbeguestbook
+
+ # change to the newly created directory
+ cd gbeguestbook
+
+ # 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/gbeguestbook/REVIEW.txt
===================================================================
--- grokapps/gbeguestbook/REVIEW.txt (rev 0)
+++ grokapps/gbeguestbook/REVIEW.txt 2008-08-15 12:40:59 UTC (rev 89878)
@@ -0,0 +1,176 @@
+Grok-by-Example: Guestbook
+==========================
+
+:Author: d2m (michael at d2m.at)
+
+A basic 'Grok Guestbook' application [source__] ported from the
+Google Appengine [GAE] demo application [source__].
+
+__ http://svn.zope.org/grokapps/gbeguestbook/src/gbeguestbook/app.py?view=markup
+__ http://code.google.com/p/googleappengine/source/browse/trunk/demos/guestbook/guestbook.py
+
+Overview
+========
+
+The application presents you a list of 10 guestbook entries both by
+authenticated and anonymous users, reverse sorted by date of creation
+and a form to submit new entries to the guestbook. Response text
+formatting is done using python string templates only.
+
+Review
+======
+
+Let's now compare how the code is layed out for both frameworks:
+
+Application object
+------------------
+
+Both frameworks use the 'Application' concept.
+Grok subclasses from both grok.Application and grok.Container::
+
+ class Application(grok.Application, grok.Container):
+ pass
+
+GAE uses the webapp.WSGIApplication and already configures the URL dispatching::
+
+ application = webapp.WSGIApplication([
+ ('/', MainPage),
+ ('/sign', Guestbook)
+ ], debug=True)
+
+Request
+-------
+
+The application accepts 2 possible requests:
+the default view and the action to post the form contents to
+
+Grok: 'index' and 'sign' are 2 grok.Views defined on the application object
+using 'grok.name'. Both views are bound to the Application object using
+'grok.context'::
+
+ class MainPage(grok.View):
+ grok.context(Application)
+ grok.name('index')
+ ...
+
+GAE: '/' and '/sign' are bound to webapp.RequestHandlers in
+'webapp.WSGIApplication'::
+
+ class MainPage(webapp.RequestHandler):
+ def get(self):
+ ...
+
+Request methods
+---------------
+
+Grok has a 'grok.REST' class that supports HTTP method dispatching, but
+it is not used in the example::
+
+ class Guestbook(grok.View):
+ ...
+ def render(self):
+ if self.request.method.upper() != 'POST':
+ return self.redirect(self.application_url())
+ ...
+
+GAE webapp.RequestHandler classes understand HTTP methods and dispatch
+accordingly::
+
+ class Guestbook(webapp.RequestHandler):
+ def post(self):
+ ...
+
+Response
+--------
+
+Grok output is collected and returned from the view 'render' method::
+
+ def render(self):
+ out=['<html><body>']
+ ...
+ return ''.join(out)
+
+GAE output is written directly to the response.out stream::
+
+ self.response.out.write('<html><body>')
+
+Models
+------
+
+Grok content objects are subclassed from grok.Model. Properties are
+defined in an Interface class which the object implements.
+
+GAE uses the db.Model class to define content objects.
+Typed properties are defined inside the class definition.
+
+Persistency
+-----------
+
+Grok content objects are instantiated, modified and finally inserted into
+a container (which here is the application object itself). A local-unique name
+must be provided on insertion. Existing objects must be deleted and reinserted.
+Properties are not validated by default.
+
+GAE content objects are instantiated, modified and finally inserted into
+the datastore. A unique key is automatically created on insertion. Existing
+objects (entities) are updated on insert. Properties are automatically
+validated on insertion.
+
+Searching
+---------
+
+Grok uses python to locate objects and create a result listing::
+
+ greetings=[(x.date, x) for x in self.context.values()]
+ greetings=list(reversed(sorted(greetings)))
+ for date,greeting in greetings[:10]:
+ ...
+
+GAE uses the built-in GQL query language the search the datastore with a SQL
+like language::
+
+ greetings = db.GqlQuery("SELECT * "
+ "FROM Greeting "
+ "ORDER BY date DESC LIMIT 10")
+ for greeting in greetings:
+ ...
+
+User
+----
+
+Grok has no fixed 'user' API, user management depends on loaded authentication
+plugins::
+
+ greeting.author = self.request.principal
+ ...
+ if IUnauthenticatedPrincipal.providedBy(greeting.author):
+ ...
+
+GAE has a 'users' API::
+
+ if users.get_current_user():
+ greeting.author = users.get_current_user()
+ ...
+ if greeting.author:
+ ...
+
+Execution
+---------
+
+Grok is a long-running process. Requests are processed, the object graph is
+traversed to a final context object and the result of calling the view on
+the context object is returned.
+
+GAE is built into a CGI-like execution model, thus calling the modules __main__
+function on each request::
+
+ def main():
+ wsgiref.handlers.CGIHandler().run(application)
+
+Overall
+-------
+
+The number of lines of code is about the same. For this small example GAE is
+simpler with respect to the number of APIs and concepts used and richer
+with respect to debugging and datasecurity.
+
Added: grokapps/gbeguestbook/bootstrap.py
===================================================================
--- grokapps/gbeguestbook/bootstrap.py (rev 0)
+++ grokapps/gbeguestbook/bootstrap.py 2008-08-15 12:40:59 UTC (rev 89878)
@@ -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/gbeguestbook/buildout.cfg
===================================================================
--- grokapps/gbeguestbook/buildout.cfg (rev 0)
+++ grokapps/gbeguestbook/buildout.cfg 2008-08-15 12:40:59 UTC (rev 89878)
@@ -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 = gbeguestbook
+site.zcml = <include package="gbeguestbook" />
+ <include package="zope.app.twisted" />
+
+ <configure i18n_domain="gbeguestbook">
+ <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 = gbeguestbook
+defaults = ['--tests-pattern', '^f?tests$', '-v']
+
+# this section named so that the i18n scripts are called bin/i18n...
+[i18n]
+recipe = lovely.recipe:i18n
+package = gbeguestbook
+domain = gbeguestbook
+location = src/gbeguestbook
+output = locales
+
+[eggbasket]
+recipe = z3c.recipe.eggbasket
+eggs = grok
+url = http://grok.zope.org/releaseinfo/grok-eggs-0.13.tgz
Added: grokapps/gbeguestbook/setup.py
===================================================================
--- grokapps/gbeguestbook/setup.py (rev 0)
+++ grokapps/gbeguestbook/setup.py 2008-08-15 12:40:59 UTC (rev 89878)
@@ -0,0 +1,36 @@
+from setuptools import setup, find_packages
+
+version = '0.1'
+
+setup(name='gbeguestbook',
+ version=version,
+ description="Grok-by-Example: Guestbook",
+ long_description="""\
+ base app: Google Appengine demo application
+ http://code.google.com/p/googleappengine/source/browse/trunk/demos/guestbook/guestbook.py
+""",
+ # 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="Michael Haubenwallner",
+ author_email="michael at d2m.at",
+ url="http://blog.d2m.at",
+ license="ZPL2",
+ 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/gbeguestbook/src/gbeguestbook/__init__.py
===================================================================
--- grokapps/gbeguestbook/src/gbeguestbook/__init__.py (rev 0)
+++ grokapps/gbeguestbook/src/gbeguestbook/__init__.py 2008-08-15 12:40:59 UTC (rev 89878)
@@ -0,0 +1 @@
+# this directory is a package
Added: grokapps/gbeguestbook/src/gbeguestbook/app.py
===================================================================
--- grokapps/gbeguestbook/src/gbeguestbook/app.py (rev 0)
+++ grokapps/gbeguestbook/src/gbeguestbook/app.py 2008-08-15 12:40:59 UTC (rev 89878)
@@ -0,0 +1,56 @@
+from datetime import datetime
+import uuid
+from zope.interface import Interface
+from zope import schema
+from zope.app.security.interfaces import IUnauthenticatedPrincipal
+import grok
+
+class Application(grok.Application, grok.Container):
+ pass
+
+class IGreeting(Interface):
+ author = schema.Field(title=u'Author')
+ content = schema.Text(title=u'Content')
+ date = schema.Datetime(title=u'Date')
+
+class Greeting(grok.Model):
+ grok.implements(IGreeting)
+
+class MainPage(grok.View):
+ grok.context(Application)
+ grok.name('index')
+
+ def render(self):
+ out=['<html><body>']
+ greetings=[(x.date, x) for x in self.context.values()]
+ greetings=list(reversed(sorted(greetings)))
+ for date,greeting in greetings[:10]:
+ if IUnauthenticatedPrincipal.providedBy(greeting.author):
+ out.append('An anonymous person wrote:')
+ else:
+ out.append('<b>%s</b> wrote:' % greeting.author.getLogin())
+ out.append('<blockquote>%s</blockquote>' %
+ greeting.content)
+ out.append("""
+ <form action="%s/sign" method="post">
+ <div><textarea name="content" rows="3" cols="60"></textarea></div>
+ <div><input type="submit" value="Sign Guestbook"></div>
+ </form>
+ </body>
+ </html>""" % self.application_url())
+ return ''.join(out)
+
+class Guestbook(grok.View):
+ grok.context(Application)
+ grok.name('sign')
+
+ def render(self):
+ if self.request.method.upper() != 'POST':
+ return self.redirect(self.application_url())
+ greeting = Greeting()
+ greeting.author = self.request.principal
+ greeting.content = self.request.get('content')
+ greeting.date = datetime.now()
+ id=str(uuid.uuid4())
+ self.context[id]=greeting
+ self.redirect(self.application_url())
Added: grokapps/gbeguestbook/src/gbeguestbook/app.txt
===================================================================
--- grokapps/gbeguestbook/src/gbeguestbook/app.txt (rev 0)
+++ grokapps/gbeguestbook/src/gbeguestbook/app.txt 2008-08-15 12:40:59 UTC (rev 89878)
@@ -0,0 +1,32 @@
+Do a functional doctest test on the app.
+========================================
+
+:Test-Layer: functional
+
+Let's first create an instance of Guestbook at the top level:
+
+ >>> from gbeguestbook.app import Application
+ >>> root = getRootFolder()
+ >>> root['app'] = Application()
+
+
+Run tests in the testbrowser
+----------------------------
+
+The zope.testbrowser.browser module exposes a Browser class that
+simulates a web browser similar to Mozilla Firefox or IE. We use that
+to test how our application behaves in a browser. For more
+information, see http://pypi.python.org/pypi/zope.testbrowser.
+
+Create a browser and visit the instance you just created:
+
+ >>> from zope.testbrowser.testing import Browser
+ >>> browser = Browser()
+ >>> browser.open('http://localhost/app')
+
+Check some basic information about the page you visit:
+
+ >>> browser.url
+ 'http://localhost/app'
+ >>> browser.headers.get('Status').upper()
+ '200 OK'
Added: grokapps/gbeguestbook/src/gbeguestbook/configure.zcml
===================================================================
--- grokapps/gbeguestbook/src/gbeguestbook/configure.zcml (rev 0)
+++ grokapps/gbeguestbook/src/gbeguestbook/configure.zcml 2008-08-15 12:40:59 UTC (rev 89878)
@@ -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/gbeguestbook/src/gbeguestbook/ftesting.zcml
===================================================================
--- grokapps/gbeguestbook/src/gbeguestbook/ftesting.zcml (rev 0)
+++ grokapps/gbeguestbook/src/gbeguestbook/ftesting.zcml 2008-08-15 12:40:59 UTC (rev 89878)
@@ -0,0 +1,35 @@
+<configure
+ xmlns="http://namespaces.zope.org/zope"
+ i18n_domain="gbeguestbook"
+ package="gbeguestbook"
+ >
+
+ <include package="grok" />
+ <include package="gbeguestbook" />
+
+ <!-- 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/gbeguestbook/src/gbeguestbook/tests.py
===================================================================
--- grokapps/gbeguestbook/src/gbeguestbook/tests.py (rev 0)
+++ grokapps/gbeguestbook/src/gbeguestbook/tests.py 2008-08-15 12:40:59 UTC (rev 89878)
@@ -0,0 +1,12 @@
+import os.path
+import z3c.testsetup
+import gbeguestbook
+from zope.app.testing.functional import ZCMLLayer
+
+
+ftesting_zcml = os.path.join(
+ os.path.dirname(gbeguestbook.__file__), 'ftesting.zcml')
+FunctionalLayer = ZCMLLayer(ftesting_zcml, __name__, 'FunctionalLayer',
+ allow_teardown=True)
+
+test_suite = z3c.testsetup.register_all_tests('gbeguestbook')
More information about the Checkins
mailing list