[svn] / grok / trunk / doc / design / grok_dev.txt Repository:
ViewVC logotype

View of /grok/trunk/doc/design/grok_dev.txt

Parent Directory Parent Directory | Revision Log Revision Log


Revision 72797 - (download) (annotate)
Fri Feb 23 23:09:33 2007 UTC (7 years, 1 month ago) by philikon
File size: 12712 byte(s)
Add big warnings signs to design docs so that noobs don't mistake them
for documentation...
==========================
Grok: Making Zope 3 Easier
==========================

**NOTE:** This document was written *before* grok was implemented.  It
has been part of the design phase during which it served well to
discuss and preserve grok's goals.  The actual implementation of grok
differs from the concrete code examples given here.  This is NOT a
grok tutorial nor should it be seen as any part of grok's current
documentation!

Introduction
============

The Grok project tries to it easier to develop Zope 3
applications. Zope 3 is typically configured using ZCML, the Zope
Configuration Markup Language. ZCML is non-Python and very explicit.

ZCML was in part inspired by the difficulties surrounding
configuration in Zope 2. Zope 2 is heavy on implicit configuration
("no docstring means no public access", "a permission used is a
permission defined"). In addition, lots of the configuration happens
in Python code, such as in the `__init__.py` of a product. These
techniques can lead to problems: hard to understand configuration code
interweaved with non-configuration code. Such code tends to be
difficult to maintain, extend, and evolve.

ZCML aims to separate configuration strongly from software to avoid
these problems. The ZCML model in combination with the notion of
explicit interfaces offers a strong model for evolving software over a
longer period of time.

ZCML has drawbacks however. Zope 3's setup for modularity, components
and evolution results in more entities for a programmer to worry
about: explicit interfaces, a multiplication of files and modules, a
large list of places to import from, a series of ZCML directives to
remember. Even though individual entities may be easier to understand,
the combination of such can make the whole more difficult to
understand, and there is just a lot to learn overall.

This is especially off-putting to developers who are trying to start
learning Zope 3. Even experienced programmers can be frustrated by so
much need to state obvious things explicitly - the multiplication of
entities in Zope 3 hurts agility of development. Finally, ZCML can be
intimidating to a Python programmer merely by being non-Python, and
XML in particular. While the latter is at least in part a superficial
concern, it is one that stops people from trying Zope 3.

Grok aims to reduce the visibility of ZCML to the Zope 3
programmer. Grok also aims to reduce the entities a programmer needs
to worry about: ideally a Zope 3 application should be able to fit
completely inside a single module. Grok does not aim to do away with
ZCML altogether: explicit configuration is valuable. Grok merely aims
to keep the advanced concepts out of the programmer's face when not
needed.

How does Grok aim accomplish these goals? We will try to follow the
guidelines that work for Ruby on Rails:

* DRY: Don't Repeat Yourself

* Convention over configuration

Grok is specified as autogenerated ZCML. This is for convenience of
specification: Grok does not need to be implemented this way but could
be calling underlying component architecture APIs directly where
needed. In fact, one of the aims of Grok is to produce more readable
errors that guide programmers into the right direction to solve their
problem.

Grok does not aim to make the programmer do correct security - an
application written with Grok without the programmer taking any
particular actions will be entirely public by default. Convenience of
development trumps security here. The idea of Grok is that the
programmer should have the easiest time possible to get an application
running first, and then can gradually tighten the application in
various ways, including security, later on.

Views
-----

To declare a browser view, a new class-level declaration `views` is
added, with as argument the class or interface the view is being
declared for::

  from zope import grok

  class FooView(grok.View):
     grok.views(Foo)

As you can see, Grok views are inherited from `grok.View`.

This is equivalent to the following ZCML::

  <browser:page
    name="FooView"
    for="Foo"
    class="FooView" 
    permission="zope.Public"
    />

Note that the name is deduced entirely from the name of the view
class. Where this is not desired, the view name can be controlled
using the `name` declaration on the view:

  from zope import grok

  class FooView(grok.View):
      grok.name("info.html")
      grok.views(Foo)

      @grok.page('info.info')
      ...

which translates to::

  <browser:page
     name="info.html"
     for="Foo"
     class="FooView"
     permission="zope.Public"
   />

It's also possible to set the skin the view is in using ``skin``
declaration::

  from zope import grok
  from myskin import IMySkin

  class FooView(grok.View):
     grok.skin(IMySkin)

XXX can we somehow stop the concept of interface from being introduced
here? Class-based skins?

Views that do not specify *any* declarations but inherit from
grok.View are also automatically registered, for everything (`*`)::

  from zope import grok

  class MyView(grok.View):
     def getData(self):
         return fadfkjdlkfjd
     __call__ = template('foo.pt')

is equivalent to the following ZCML::

  <browser:page
     name="MyView"
     for="*"
     class="MyView"
     permission="zope.Public"
     />

View Security 
-------------

The default permission on views generated by Grok is `zope.Public`. At
first glance, this seems to be going back to the bad old insecure days
of Zope 2. Zope 3 has a different security model though: content
objects can declare their own security. Since Grok declares content
objects entirely public by default as well however, that is not a very
strong argument.

The more important argument for this "wide-open" default is that
dealing with security issues early on in application development tends
to be very painful and distracting for most application developers,
breaking their flow. The temptation is therefore large to just give
any permissions that will permit the developer to continue. Security
issues are typically ignored until a later stage of development. We
recognize this pattern of development, and we can't really enforce
developers doing security right anyway, so Grok doesn't aim to try.

Of course it is possible to override the default, using a class
declaration on the view called `permission`::

  from zope import grok

  class MyView:
     grok.views(SomeContent)
     grok.permission('zope.ManageContent')

Non-existing permissions are not created automatically - new
permissions have to be declared explicitly, using ZCML.

Finally, Grok also offers a "security testing" mode, where the default
permission of all views in a package is specified by the
developer. This can be used at a later stage during development to
flush out any security problems.

XXX explain spelling for that

Class security
--------------

Attributes, both reading and writing on content are protected with the
`zope.Public` permission by default. The aim of Grok is to get out of
the programmers hair.

How does Grok determine that something is a content class and thus
should have its attributes made public?

Grok determines something is a content class if:

* it has views that directly view it

* it is a subclass of a class that has views

* it has an interface that has views registered for it

For the purposes of security, views registered for `*` do *not* count
as views registered for a class or interface.

To restrict the permission of a content class explicitly, the same
`permission` class declaration can be used as the one defined for
views. This declaration sets the default access permission for *all*
attributes of the class::

  from zope import grok

  class FooContent:
     grok.permission('zope.ManageContent')

It is often desirable to make an exception for some attributes, however::

  from zope ipmort grok

  class FooContent:
     grok.permission('myapp.View')

     @@grok.protected('myapp.Edit')
     def protectedMethod(self):
         pass

     @@grok.private()
     def privateMethod(self):
         pass

     def _alsoPrivate():
         pass

As soon as specific declarations are used to restrict the access to
various methods, the default view permission of `zope.Public` does not
apply anymore for that class.

Importing 
---------

As you've seen from the examples, every import in a simple Grok
application will be from the `zope.grok` package. This means that the
developer does not need to remember a complicated set of imports from
a wide-ranging set of packages just to build a simple package.

This is the opposite of the current trend in Zope 3 development:
imports from `zope.app.zapi`, intended to be an easy location to get
important stuff are replaced with the imports from the real location.

`zope.app.zapi` as an approach to simplify things failed for a number
of reasons: it not very coherent, so hard to predict what ended up
there, and it was incomplete: to build a real Zope 3 application you
always needed to import from places beyond `zope.app.zapi`. In
contrast, `zope.grok` aims to provide a relatively small number of
names to use, and that should be *all* needed to produce a simple
`zope.grok`-based application.

Of course as a grok-based application grows developers will find
themselves importing from other package. This should be a voyage of
gradual discovery for the developer. There should be no need to go the
whole way right away.

XXX possible exceptions: zope.schema, zope.formlib

Schema-classes
--------------

It is possible to define the schema of a class directly::

  from zope import grok

  class MyContent(grok.Content):
     class data:
         foo = TextLine(u'Foo')

Fields defined are automatically created as attributes on instances
using the defaults specified by the fields (the default default if no
default is specified).

XXX schema really should accept pure ascii strings in all cases
    instead of unicode strings, and not *require* a u'' in front of
    strings and give an obscure error. If you need non-ascii, *then*
    you worry about unicode. Yet another thing less to explain to
    beginners however.

XXX we cannot use the name `schema` for the schema inner class as that
    would conflict with `from zope import schema`...

By default the schema is readable and writeable by all.

The schema read and write permissions for schema fields can be set
using special class declarations::

  from zope import grok

  class MyContent(grok.Content):
     class data:
        read_permission('myapp.View')
        write_permission('myapp.Edit')

        foo = TextLine(u'Foo')

XXX want to handle the usecase of a class with multiple related
    schemas that have an inheritance relationship to each other?

No interfaces necessary
-----------------------

A Grok application can be written without a single interface in
sight. No interfaces means less entities for the developer to care
about, and the goal of grok is to avoid having to worry about
interfaces when building an application. Interfaces can always be
added later during the evolution of an application.

XXX exception for skins at present..

As an application grows a developer can start introducing interfaces:
grok does not *stop* the use of interfaces.

Adapters
--------

XXX is this something to be treated by grok or is this an "advanced
topic" outside the purview of grok? It should be easy to recognize
adapters that use `adapts()` and automatically register them.

Forms
-----

Let's see how `zope.formlib` fits in with grok::

  from zope import schema
  from zope import grok

  class MyContent(grok.Content):
     class data:
         name = schema.TextLine("Name")
         age = schema.Int("Age")

  class MyContentView(grok.EditForm):
     grok.name('edit.html')
     grok.views(MyContent)

     # XXX can it be automatically deduced where the fields come from
     # at this point?
     form_fields = grok.Fields()

     grok.action('Ok')
     def handle_input(self, action, data):
        pass

XXX should be really be covering up formlib.form? ``grok.Fields()``
    seems to imply we might have to.

Templates
---------

This sets up a template to use::

  from zope import grok

  class MyView(grok.View):
     grok.template('mytemplate.pt')

XXX Sensible default behavior:

  * use template for __call__ if no __call__ defined

  * template/update pattern?

Menus
-----

XXX How to make things show up in menus? When we create a new content
object we at least want an easy way to create it, though not
necessarily through the ZMI. Menu class declaration?

TDB
---

* Easy way to generate add views

* easy local utility registration?

* bread - easy creation of tables, catalog-based search


zope.org Infrastructure
ViewVC Help
Powered by ViewVC 1.0.3