[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