[Checkins]
SVN: grok/branches/jspaans-traversableattrs/doc/grok_overview.rst
Add grok.traversable documentation to the developer's notes
Jasper Spaans
jspaans at thehealthagency.com
Fri May 2 05:50:07 EDT 2008
Log message for revision 86064:
Add grok.traversable documentation to the developer's notes
Changed:
U grok/branches/jspaans-traversableattrs/doc/grok_overview.rst
-=-
Modified: grok/branches/jspaans-traversableattrs/doc/grok_overview.rst
===================================================================
--- grok/branches/jspaans-traversableattrs/doc/grok_overview.rst 2008-05-02 09:45:38 UTC (rev 86063)
+++ grok/branches/jspaans-traversableattrs/doc/grok_overview.rst 2008-05-02 09:50:06 UTC (rev 86064)
@@ -1,4 +1,4 @@
-Grok Developer's Notes
+Grok Developer's Notes
======================
This document is a developer's overview of Grok. It is not intended to
@@ -72,9 +72,9 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Models in Grok are automatically supplied with a ``__parent__`` and a
-``__name__`` attribute.
+``__name__`` attribute.
-* ``__parent__`` points to object this object is in. If the object is in
+* ``__parent__`` points to object this object is in. If the object is in
a container, this is the container.
* ``__name__`` is the name this object has in a URL (and in its
@@ -138,6 +138,27 @@
object asked for. Grok then falls back on default behavior, which in
this case would mean a ``404 Not Found`` error.
+Traversable attributes
+~~~~~~~~~~~~~~~~~~~~~~
+
+In some cases, you want to traverse to attributes or methods of your
+``grok.Model``. This can be done easily using the ``grok.traversable``
+directive::
+
+ class Mammoth(grok.Model):
+ grok.traversable('trunk')
+
+ trunk = Trunk()
+
+ class MammothView(grok.View):
+ grok.context(Mammoth)
+
+ def render(self):
+ return "I'm a mammoth!"
+
+Now, if traversing to http://localhost/mammoth/trunk , a Trunk()
+object will be exposed at that URL.
+
Views
-----
@@ -146,10 +167,10 @@
is a view? A view is a class that represents a model in some way. It
creates a user interface of some sort (typically HTML) for a model. A
single model can have more than one view. It looks like this::
-
+
class Index(grok.View):
grok.context(Application)
-
+
def render(self):
return "This is the application"
@@ -170,7 +191,7 @@
What happens when you go to this URL is that Grok instantiates the
``Index`` class, creating a ``Index`` instance. View instances have
-a number of attributes by default:
+a number of attributes by default:
* ``context``, the model instance that the view is presenting.
@@ -188,7 +209,7 @@
class Edit(grok.View):
grok.context(Application)
-
+
def render(self):
return "This is the edit screen for the application"
@@ -203,7 +224,7 @@
class SomeImpossiblyLongClassName(grok.View):
grok.context(Application)
grok.name('edit')
-
+
def render(self):
return "This is the edit screen for the application"
@@ -314,7 +335,7 @@
~~~~~~~~~~~~~~~~~~
Views have a special method called ``url()`` that can be used to
-create URLs to objects. The ``url`` method takes zero, one or two
+create URLs to objects. The ``url`` method takes zero, one or two
arguments and an additional optional keyword argument 'data' that
is converted into a CGI query string appended to the URL::
@@ -328,12 +349,12 @@
* self.url(object, u"name") - URL to the provided object, with
``/name`` appended, to point to a view or subobject
of the provided object.
-
-* self.url(object, u"name", data={'name':'Peter', 'age':28})
+
+* self.url(object, u"name", data={'name':'Peter', 'age':28})
- URL to the provided object, with ``/name`` appended
with '?name=Peter&age=28' at the end.
-
-* self.url(data={'name':u'Andr\xe9', 'age:int':28}) - URL to the provided
+
+* self.url(data={'name':u'Andr\xe9', 'age:int':28}) - URL to the provided
object with '?name=Andre%C3%A9'&age%3Aint=28'.
From the view, this is accessed through ``self.url()``. From the
@@ -368,7 +389,7 @@
``context``, which should normally be used).
``__name__`` is the name of the view in the URL.
-
+
The ``@@`` thing
~~~~~~~~~~~~~~~~
@@ -487,7 +508,7 @@
def size(self):
total = 0
for obj in self.values():
- total += obj.size()
+ total += obj.size()
return total
For simple cases this is fine, but for larger applications this can
@@ -531,36 +552,36 @@
elif isintance(self.context, Container):
total = 0
for obj in self.context.values():
- total += Sized(obj).size()
+ total += Sized(obj).size()
return total
-
+
Instead, we can create a smart ``sized`` factory that does this
switch-on-type behavior instead, keeping our adapters clean::
class DocumentSized(object):
def __init__(self, context):
self.context = context
-
+
def sized(self):
return len(self.context.text.encode('UTF-8'))
class ImageSized(object):
def __init__(self, context):
self.context = context
-
+
def sized(self):
return len(self.context.data)
class ContainerSized(object):
def __init__(self, context):
self.context = context
-
+
def sized(self):
total = 0
for obj in self.context.values():
- total += sized(obj).size()
+ total += sized(obj).size()
return total
-
+
def sized(context):
if isinstance(context, Document):
return DocumentedSized(context)
@@ -568,7 +589,7 @@
return ImageSized(context)
elif isinstance(context, Container):
return ContainerSized(context)
-
+
We can now call ``sized`` for a content object and get an object back
that implements the "sized API"::
@@ -595,21 +616,21 @@
class DocumentSized(grok.Adapter):
grok.context(Document)
grok.provides(ISized)
-
+
def sized(self):
return len(self.context.text.encode('UTF-8'))
class ImageSized(grok.Adapter):
grok.context(Image)
grok.provides(ISized)
-
+
def sized(self):
return len(self.context.data)
class ContainerSized(grok.Adapter):
grok.context(Container)
grok.provides(ISized)
-
+
def sized(self):
total = 0
for obj in self.context.values():
@@ -646,7 +667,7 @@
class Cow(object):
grok.implements(IAnimal)
-
+
def __init__(self, name):
self.name = name
@@ -700,7 +721,7 @@
grok.name('somename')
grok.context(SomeClass)
grok.provides(ISomeInterface)
-
+
Actually all adapters are named: by default the name of an adapter is
the empty string.
@@ -709,8 +730,8 @@
``zope.component`` package, in particular ``getAdapter``::
from zope import component
-
- my_adapter = component.getAdapter(some_object, ISomeInterface,
+
+ my_adapter = component.getAdapter(some_object, ISomeInterface,
name='somename')
``getAdapter`` can also be used to look up unnamed adapters, as an
@@ -731,7 +752,7 @@
class MyMultiAdapter(grok.MultiAdapter):
grok.adapts(SomeClass, AnotherClass)
grok.provides(ISomeInterface)
-
+
def __init__(some_instance, another_instance):
self.some_interface = some_instance
self.another_instance = another_instance
@@ -763,7 +784,7 @@
A view in Grok is in fact a named multi adapter, providing the base
interface (``Interface``). This means that a view in Grok can be
looked up in code by the following call::
-
+
from zope.interface import Interface
view = component.getMultiAdapter((object, request), Interface, name="index")
@@ -917,7 +938,7 @@
Fired when an object was copied. It is a specialization of
``IObjectCreatedEvent`` that is fired by the system if you use the
-``zope.copypastemove`` functionality.
+``zope.copypastemove`` functionality.
Besides the ``object`` attribute it shares with
``IObjectCreattedEvent``, it has also has the ``original`` attribute,
@@ -951,7 +972,7 @@
This subclassing from ``ObjectEvent`` is not required; if your event
isn't about an object, you can choose to design your event class
entirely yourself. See ``zope.sendmail`` for the construction of mail sending
-events for an example.
+events for an example.
Interfaces for events
~~~~~~~~~~~~~~~~~~~~~
@@ -990,7 +1011,7 @@
* The system can inspect the interfaces a particular object provides,
and treat them as an abstract form of classes for registration
- purposes.
+ purposes.
Interfaces make it possible to use a generic framework's pluggability
points with confidence: you can clearly see what you are supposed to
@@ -1060,7 +1081,7 @@
class NonSubclassObjectEvent(object):
grok.implements(IObjectEvent)
-
+
def __init__(self, object):
self.object = object
@@ -1093,7 +1114,7 @@
grok.provides(ISortedKeys)
def sortedKeys(self):
- return sorted(self.context.keys())
+ return sorted(self.context.keys())
Interfaces and views
~~~~~~~~~~~~~~~~~~~~
@@ -1105,7 +1126,7 @@
class Keys(grok.View):
grok.context(IContainer)
-
+
def render(self):
return ', '.join(ISortedKeysAdapter(self.context).sortedKeys())
@@ -1119,7 +1140,7 @@
class Layout(grok.View):
grok.context(Interface)
-
+
with a template ``layout.pt`` associated to it.
You can then use these macros in any page template anywhere by
@@ -1139,7 +1160,7 @@
from zope.interface import Interface
from zope import schema
-
+
class ISpecies(Interface):
name = schema.TextLine(u"Animal species name")
scientific_name = schema.TextLine(u"Scientific name")
@@ -1160,7 +1181,7 @@
class Species(grok.Form):
form_fields = grok.Fields(ISpecies)
-
+
@grok.action(u"Save form")
def handle_save(self, **data):
print data['name']
@@ -1170,7 +1191,7 @@
What is going on here? Firstly we use a special base class called
``grok.Form``. A form is a special kind of ``grok.View``, and
associates the same way (using ``grok.context``). A form expects two
-things::
+things:
* a ``form_fields`` attribute. Above we see the most common way to construct
this attribute, using ``grok.Fields`` on the interface.
@@ -1203,7 +1224,7 @@
grok.context(SpeciesContainer)
form_fields = grok.Fields(ISpecies)
-
+
@grok.action(u"Add species")
def add_species(self, **data):
# create a species instance
@@ -1230,13 +1251,13 @@
class Edit(grok.EditForm):
grok.context(Species)
-
+
form_fields = grok.Fields(ISpecies)
@grok.action(u"Edit species")
def edit_species(self, **data):
self.applyData(species, **data)
-
+
Forms are self-submitting, so this will show the edit form again. If
you want to display another page, you can redirect the browser as we
showed for the add form previously.
@@ -1252,7 +1273,7 @@
class Display(grok.DisplayForm):
grok.context(Species)
-
+
form_fields = grok.Fields(ISpecies)
The user can now go to ``myspecies/display`` to look at the species.
@@ -1289,12 +1310,12 @@
<!-- the title of the field -->
<span i18n:translate="" tal:content="widget/label">label</span>
</label>
-
+
<!-- render the HTML widget -->
<div class="widget" tal:content="structure widget">
<input type="text" />
</div>
-
+
<!-- render any field specific validation error from a previous
form submit next to the field -->
<div class="error" tal:condition="widget/error">
@@ -1315,10 +1336,9 @@
<tal:block content="widget/label" />
<input tal:replace="structure widget" />
</tal:block>
-
+
<!-- render all the action submit buttons -->
<span class="actionButtons" tal:condition="view/availableActions">
<input tal:repeat="action view/actions"
tal:replace="structure action/render" />
</span>
-
More information about the Checkins
mailing list