Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

First cut at r.b.formish package; no tests. Ignore me.

  • Loading branch information...
commit 6c3a2b94024e399692fa8822be4d69b1d5ee8279 0 parents
Chris McDonough authored
7 CHANGES.txt
... ... @@ -0,0 +1,7 @@
  1 +repoze.atemplate Changelog
  2 +==========================
  3 +
  4 +0.1 (unreleaesd)
  5 +----------------
  6 +
  7 +- Initial release.
3  COPYRIGHT.txt
... ... @@ -0,0 +1,3 @@
41 LICENSE.txt
... ... @@ -0,0 +1,41 @@
  1 +License
  2 +
  3 + A copyright notice accompanies this license document that identifies
  4 + the copyright holders.
  5 +
  6 + Redistribution and use in source and binary forms, with or without
  7 + modification, are permitted provided that the following conditions are
  8 + met:
  9 +
  10 + 1. Redistributions in source code must retain the accompanying
  11 + copyright notice, this list of conditions, and the following
  12 + disclaimer.
  13 +
  14 + 2. Redistributions in binary form must reproduce the accompanying
  15 + copyright notice, this list of conditions, and the following
  16 + disclaimer in the documentation and/or other materials provided
  17 + with the distribution.
  18 +
  19 + 3. Names of the copyright holders must not be used to endorse or
  20 + promote products derived from this software without prior
  21 + written permission from the copyright holders.
  22 +
  23 + 4. If any files are modified, you must cause the modified files to
  24 + carry prominent notices stating that you changed the files and
  25 + the date of any change.
  26 +
  27 + Disclaimer
  28 +
  29 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND
  30 + ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  31 + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
  32 + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  33 + HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  34 + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  35 + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  36 + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  37 + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
  38 + TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
  39 + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  40 + SUCH DAMAGE.
  41 +
4 README.txt
... ... @@ -0,0 +1,4 @@
  1 +repoze.atemplate README
  2 +=======================
  3 +
  4 +Please see docs/index.rst for the documentation.
BIN  docs/.static/logo_hi.gif
22 docs/.static/repoze.css
... ... @@ -0,0 +1,22 @@
  1 +@import url('default.css');
  2 +body {
  3 + background-color: #006339;
  4 +}
  5 +
  6 +div.document {
  7 + background-color: #dad3bd;
  8 +}
  9 +
  10 +div.sphinxsidebar h3, h4, h5, a {
  11 + color: #127c56 !important;
  12 +}
  13 +
  14 +div.related {
  15 + color: #dad3bd !important;
  16 + background-color: #00744a;
  17 +}
  18 +
  19 +div.related a {
  20 + color: #dad3bd !important;
  21 +}
  22 +
70 docs/Makefile
... ... @@ -0,0 +1,70 @@
  1 +# Makefile for Sphinx documentation
  2 +#
  3 +
  4 +# You can set these variables from the command line.
  5 +SPHINXOPTS =
  6 +SPHINXBUILD = sphinx-build
  7 +PAPER =
  8 +
  9 +# Internal variables.
  10 +PAPEROPT_a4 = -D latex_paper_size=a4
  11 +PAPEROPT_letter = -D latex_paper_size=letter
  12 +ALLSPHINXOPTS = -d .build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
  13 +
  14 +.PHONY: help clean html web pickle htmlhelp latex changes linkcheck
  15 +
  16 +help:
  17 + @echo "Please use \`make <target>' where <target> is one of"
  18 + @echo " html to make standalone HTML files"
  19 + @echo " pickle to make pickle files (usable by e.g. sphinx-web)"
  20 + @echo " htmlhelp to make HTML files and a HTML help project"
  21 + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
  22 + @echo " changes to make an overview over all changed/added/deprecated items"
  23 + @echo " linkcheck to check all external links for integrity"
  24 +
  25 +clean:
  26 + -rm -rf .build/*
  27 +
  28 +html:
  29 + mkdir -p .build/html .build/doctrees
  30 + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) .build/html
  31 + @echo
  32 + @echo "Build finished. The HTML pages are in .build/html."
  33 +
  34 +pickle:
  35 + mkdir -p .build/pickle .build/doctrees
  36 + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) .build/pickle
  37 + @echo
  38 + @echo "Build finished; now you can process the pickle files or run"
  39 + @echo " sphinx-web .build/pickle"
  40 + @echo "to start the sphinx-web server."
  41 +
  42 +web: pickle
  43 +
  44 +htmlhelp:
  45 + mkdir -p .build/htmlhelp .build/doctrees
  46 + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) .build/htmlhelp
  47 + @echo
  48 + @echo "Build finished; now you can run HTML Help Workshop with the" \
  49 + ".hhp project file in .build/htmlhelp."
  50 +
  51 +latex:
  52 + mkdir -p .build/latex .build/doctrees
  53 + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) .build/latex
  54 + @echo
  55 + @echo "Build finished; the LaTeX files are in .build/latex."
  56 + @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
  57 + "run these through (pdf)latex."
  58 +
  59 +changes:
  60 + mkdir -p .build/changes .build/doctrees
  61 + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) .build/changes
  62 + @echo
  63 + @echo "The overview file is in .build/changes."
  64 +
  65 +linkcheck:
  66 + mkdir -p .build/linkcheck .build/doctrees
  67 + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) .build/linkcheck
  68 + @echo
  69 + @echo "Link check complete; look for any errors in the above output " \
  70 + "or in .build/linkcheck/output.txt."
14 docs/api.rst
Source Rendered
... ... @@ -0,0 +1,14 @@
  1 +API Documentation for repoze.atemplate
  2 +======================================
  3 +
  4 +Interfaces
  5 +----------
  6 +
  7 +Document interfaces here. You may want to use the ``.. autointerface::``
  8 +directive provided by :mod:`repoze.sphinx.autointerface`, in which case
  9 +you will need to add it to the extensions in `conf.py`.
  10 +
  11 +Exceptions
  12 +----------
  13 +
  14 +Document any custom exceptions here.
185 docs/conf.py
... ... @@ -0,0 +1,185 @@
  1 +# -*- coding: utf-8 -*-
  2 +#
  3 +# repoze.bfg.formish documentation build configuration file
  4 +#
  5 +# This file is execfile()d with the current directory set to its containing
  6 +# dir.
  7 +#
  8 +# The contents of this file are pickled, so don't put values in the
  9 +# namespace that aren't pickleable (module imports are okay, they're
  10 +# removed automatically).
  11 +#
  12 +# All configuration values have a default value; values that are commented
  13 +# out serve to show the default value.
  14 +
  15 +import sys, os
  16 +
  17 +# If your extensions are in another directory, add it here. If the
  18 +# directory is relative to the documentation root, use os.path.abspath to
  19 +# make it absolute, like shown here.
  20 +#sys.path.append(os.path.abspath('some/directory'))
  21 +
  22 +# General configuration
  23 +# ---------------------
  24 +
  25 +# Add any Sphinx extension module names here, as strings. They can be
  26 +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
  27 +extensions = ['sphinx.ext.autodoc']
  28 +
  29 +# Add any paths that contain templates here, relative to this directory.
  30 +templates_path = ['.templates']
  31 +
  32 +# The suffix of source filenames.
  33 +source_suffix = '.rst'
  34 +
  35 +# The master toctree document.
  36 +master_doc = 'index'
  37 +
  38 +# General substitutions.
  39 +project = 'repoze.bfg.formish'
  40 +copyright = '2009, Repoze Developers <repoze-dev@lists.repoze.org>'
  41 +
  42 +# The default replacements for |version| and |release|, also used in various
  43 +# other places throughout the built documents.
  44 +#
  45 +# The short X.Y version.
  46 +version = '0.1'
  47 +# The full version, including alpha/beta/rc tags.
  48 +release = '0.1'
  49 +
  50 +# There are two options for replacing |today|: either, you set today to
  51 +# some non-false value, then it is used:
  52 +#today = ''
  53 +# Else, today_fmt is used as the format for a strftime call.
  54 +today_fmt = '%B %d, %Y'
  55 +
  56 +# List of documents that shouldn't be included in the build.
  57 +#unused_docs = []
  58 +
  59 +# List of directories, relative to source directories, that shouldn't be
  60 +# searched for source files.
  61 +#exclude_dirs = []
  62 +
  63 +# The reST default role (used for this markup: `text`) to use for all
  64 +# documents.
  65 +#default_role = None
  66 +
  67 +# If true, '()' will be appended to :func: etc. cross-reference text.
  68 +#add_function_parentheses = True
  69 +
  70 +# If true, the current module name will be prepended to all description
  71 +# unit titles (such as .. function::).
  72 +#add_module_names = True
  73 +
  74 +# If true, sectionauthor and moduleauthor directives will be shown in the
  75 +# output. They are ignored by default.
  76 +#show_authors = False
  77 +
  78 +# The name of the Pygments (syntax highlighting) style to use.
  79 +pygments_style = 'sphinx'
  80 +
  81 +
  82 +# Options for HTML output
  83 +# -----------------------
  84 +
  85 +# The style sheet to use for HTML and HTML Help pages. A file of that name
  86 +# must exist either in Sphinx' static/ path, or in one of the custom paths
  87 +# given in html_static_path.
  88 +html_style = 'repoze.css'
  89 +
  90 +# The name for this set of Sphinx documents. If None, it defaults to
  91 +# "<project> v<release> documentation".
  92 +#html_title = None
  93 +
  94 +# A shorter title for the navigation bar. Default is the same as
  95 +# html_title.
  96 +#html_short_title = None
  97 +
  98 +# The name of an image file (within the static path) to place at the top of
  99 +# the sidebar.
  100 +html_logo = '.static/logo_hi.gif'
  101 +
  102 +# The name of an image file (within the static path) to use as favicon of
  103 +# the docs. This file should be a Windows icon file (.ico) being 16x16 or
  104 +# 32x32 pixels large.
  105 +#html_favicon = None
  106 +
  107 +# Add any paths that contain custom static files (such as style sheets)
  108 +# here, relative to this directory. They are copied after the builtin
  109 +# static files, so a file named "default.css" will overwrite the builtin
  110 +# "default.css".
  111 +html_static_path = ['.static']
  112 +
  113 +# If not '', a 'Last updated on:' timestamp is inserted at every page
  114 +# bottom, using the given strftime format.
  115 +html_last_updated_fmt = '%b %d, %Y'
  116 +
  117 +# If true, SmartyPants will be used to convert quotes and dashes to
  118 +# typographically correct entities.
  119 +#html_use_smartypants = True
  120 +
  121 +# Custom sidebar templates, maps document names to template names.
  122 +#html_sidebars = {}
  123 +
  124 +# Additional templates that should be rendered to pages, maps page names to
  125 +# template names.
  126 +#html_additional_pages = {}
  127 +
  128 +# If false, no module index is generated.
  129 +#html_use_modindex = True
  130 +
  131 +# If false, no index is generated.
  132 +#html_use_index = True
  133 +
  134 +# If true, the index is split into individual pages for each letter.
  135 +#html_split_index = False
  136 +
  137 +# If true, the reST sources are included in the HTML build as
  138 +# _sources/<name>.
  139 +#html_copy_source = True
  140 +
  141 +# If true, an OpenSearch description file will be output, and all pages
  142 +# will contain a <link> tag referring to it. The value of this option must
  143 +# be the base URL from which the finished HTML is served.
  144 +#html_use_opensearch = ''
  145 +
  146 +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
  147 +#html_file_suffix = ''
  148 +
  149 +# Output file base name for HTML help builder.
  150 +htmlhelp_basename = 'atemplatedoc'
  151 +
  152 +
  153 +# Options for LaTeX output
  154 +# ------------------------
  155 +
  156 +# The paper size ('letter' or 'a4').
  157 +#latex_paper_size = 'letter'
  158 +
  159 +# The font size ('10pt', '11pt' or '12pt').
  160 +#latex_font_size = '10pt'
  161 +
  162 +# Grouping the document tree into LaTeX files. List of tuples
  163 +# (source start file, target name, title,
  164 +# author, document class [howto/manual]).
  165 +latex_documents = [
  166 + ('index', 'rbfgformish.tex', 'repoze.bfg.formish Documentation',
  167 + 'Repoze Developers', 'manual'),
  168 +]
  169 +
  170 +# The name of an image file (relative to this directory) to place at the
  171 +# top of the title page.
  172 +latex_logo = '.static/logo_hi.gif'
  173 +
  174 +# For "manual" documents, if this is true, then toplevel headings are
  175 +# parts, not chapters.
  176 +#latex_use_parts = False
  177 +
  178 +# Additional stuff for the LaTeX preamble.
  179 +#latex_preamble = ''
  180 +
  181 +# Documents to append as an appendix to all manuals.
  182 +#latex_appendices = []
  183 +
  184 +# If false, no module index is generated.
  185 +#latex_use_modindex = True
411 docs/index.rst
Source Rendered
... ... @@ -0,0 +1,411 @@
  1 +Documentation for repoze.bfg.formish
  2 +====================================
  3 +
  4 +:mod:`repoze.bfg.formish` is a package which provides
  5 +:mod:`repoze.bfg` bindings for the :term:`Formish` package, which is
  6 +an excellent form generation and validation package for Python.
  7 +
  8 +This package provides:
  9 +
  10 +- A ``chameleon.zpt`` Formish "renderer" class.
  11 +
  12 +- A set of ``chameleon.zpt`` templates which implement the default
  13 + Formish widgetset.
  14 +
  15 +- A convenience ``Form`` implementation, which inherits from the
  16 + standard ``formish.Form`` class, but uses the ``chameleon_zpt``
  17 + renderer instead of the default Formish ``mako`` renderer.
  18 +
  19 +- A ``formish:add_template_path`` ZCML directive which allows for the
  20 + configuration of override Formish template locations.
  21 +
  22 +- A ``formish:form`` ZCML directive which can be used to configure a
  23 + Formish form "semi-declaratively".
  24 +
  25 +Schemas
  26 +-------
  27 +
  28 +Schemas are :term:`Schemaish` schema definitions, usually defined at
  29 +module scope as a class that inherits from the class
  30 +``schemaish.Structure``. For example:
  31 +
  32 +.. code-block:: python
  33 + :linenos:
  34 +
  35 + import schemaish
  36 + from validatish import validator
  37 +
  38 + class AddCommunitySchema(schemaish.Structure):
  39 + title = schemaish.String(validator=validator.Required())
  40 + description = schemaish.String(
  41 + description = ('This description will appear in search results and '
  42 + 'on the community listing page. Please limit your '
  43 + ' description to 100 words or less'),
  44 + validator = validator.All(validator.Length(max=500),
  45 + validator.Required())
  46 + )
  47 + text = schemaish.String(
  48 + description = ('This text will appear on the Overview page for this '
  49 + 'community. You can use this to describe the '
  50 + 'community or to make a special announcement.'))
  51 + tools = schemaish.Sequence(
  52 + attr = schemaish.String(),
  53 + description = 'Select which tools to enable on this community.')
  54 +
  55 +A schema describes the data types of fields associated with a form, as
  56 +well as any validation constraints for individual fields on the form.
  57 +A schema does not describe the user interface elements associated with
  58 +the fields it describes. Schemas are consulted by *forms*, which are
  59 +separate entities in the :term:`Formish` design. A form is
  60 +responsible for describing the user interface associated with a
  61 +schema.
  62 +
  63 +``formish:form`` ZCML Directive
  64 +-------------------------------
  65 +
  66 +When the ``meta.zcml`` of :mod:`repoze.bfg.formish` is included
  67 +within your :mod:`repoze.bfg` application, you can make use of the
  68 +``formish:form`` ZCML directive. The ZCML directive uses two major
  69 +concepts: schemas and actions.
  70 +
  71 +You must add the following to your application's ``configure.zcml`` to
  72 +use the ``formish:form`` directive:
  73 +
  74 +.. code-block:: xml
  75 + :linenos:
  76 +
  77 + <include package="repoze.bfg.formish" file="meta.zcml"/>
  78 +
  79 +You refer to the schema defined in a Python within a ``formish:form``
  80 +directive using the ``schema`` attribute, which is a dotted Python
  81 +name referring to the schema class:
  82 +
  83 +.. code-block:: xml
  84 + :linenos:
  85 +
  86 + <formish:form
  87 + for=".models.MyModel"
  88 + name="add_community.html"
  89 + schema=".forms.AddCommunitySchema"
  90 + template="templates/form_template.pt"
  91 + handler=".forms.Show"/>
  92 +
  93 +A ``formish:form`` configures one or more special :mod:`repoze.bfg`
  94 +"view" callables that render a form.
  95 +
  96 +The ``for`` and ``name`` attributes of a ``formish:form`` tag mirror
  97 +the meaning of the meanings of these names in :mod:`repoze.bfg`
  98 +``view`` ZCML directive. ``for`` represents the class or interface
  99 +which the context must implement for this view to be invoked.
  100 +``name`` is the view name.
  101 +
  102 +The above example assumes that there is a ``forms`` module which lives
  103 +in the same directory as the ``configure.zcml`` of your application,
  104 +and that it contains a schema definition named ``AddCommunitySchema``.
  105 +This is the value represented by the ``schema`` attribute above.
  106 +
  107 +It also names a Chameleon ZPT template via its ``template`` attribute
  108 +which will be used to render the form when it is first presented, or
  109 +when form validation fails. The template is either a BFG "resource
  110 +specification" or an absolute or ZCML-package-relative path to an
  111 +on-disk template.
  112 +
  113 +The ``handler`` attribute names a "default" *handler* for this form
  114 +definition. The definition of a *handler* is discussed below; for now
  115 +please assume it is a dotted Python name which specifies a special
  116 +kind of callable. The above example names it as ``.forms.Show``,
  117 +which makes the assumption that a handler callable named ``Show``
  118 +lives in the package containing the ``configure.zcml`` file in a
  119 +module named ``forms``.
  120 +
  121 +Actions
  122 +-------
  123 +
  124 +An *action* is a subdirective of the ``formish:form`` directive. It
  125 +names a *handler*, a *param*, and a *title*. For example:
  126 +
  127 +.. code-block:: xml
  128 + :linenos:
  129 +
  130 + <formish:form
  131 + for=".models.MyModel"
  132 + name="add_community.html"
  133 + schema=".forms.AddCommunitySchema"
  134 + template="templates/form_template.pt"
  135 + handler=".forms.Show">
  136 +
  137 + <action
  138 + handler=".forms.Cancel"
  139 + param="form_cancel"
  140 + title="Cancel"
  141 + />
  142 +
  143 + </formish:form>
  144 +
  145 +Any number of ``action`` tags can be present within a ``formish:form``
  146 +tag.
  147 +
  148 +Each ``action`` tag represents a submit button at the bottom of the
  149 +form that will be given an HTML "value" matching the ``param``
  150 +attribute. When this button is pressed, the value of ``param`` will
  151 +be present in the ``request.params`` dictionary. The *value* of the
  152 +button (the text visible to the user) will be the value of the
  153 +``title`` attribute.
  154 +
  155 +Each action additionally must specify a ``handler`` attribute, which
  156 +is the dotted Python to a *factory* which has the capability to
  157 +influence the painting of the form as well as what happens when form
  158 +validation succeeds. A particular handler is invoked only when the
  159 +value of the ``param`` attribute for its action is present as a key in
  160 +the ``request.params`` dictionary.
  161 +
  162 +If there is no key in in ``request.params`` dictionary which matches
  163 +the ``param`` of a particular form's action, the handler of the form
  164 +itself is called. For example, if the form we're defining above is
  165 +invoked with a request has a params dict that has the value
  166 +``form_cancel``, the ``.forms.Cancel`` handler is called. But if
  167 +``form_cancel`` is not present, the ``.forms.Show`` handler is called.
  168 +
  169 +Handlers
  170 +--------
  171 +
  172 +A :term:`handler` is the dotted name to a Python callable which must
  173 +accept four arguments: ``context``, ``request``, ``schema`` and
  174 +``form``. It must *return* a callable object which must accept a
  175 +single argument: ``converted``, which will be a dictionary represented
  176 +the values present in the form post when form validation was
  177 +successful. The callable object which a handler returns is called
  178 +when form validation succeeds. It is known as the *success* or
  179 +*success callable*. A handler is usually represented a class, with
  180 +its ``__init__`` method representing the initialization, and its
  181 +``__call__`` method representing the success callable. For example:
  182 +
  183 +.. code-block:: python
  184 + :linenos:
  185 +
  186 + from webob.exc import HTTPFound
  187 + from repoze.bfg.url import model_url
  188 +
  189 + class Cancel(object):
  190 + def __init__(self, context, request, schema, form):
  191 + self.context = context
  192 + self.request = request
  193 + self.schema = schema
  194 + self.form = form
  195 +
  196 + def __call__(self, converted):
  197 + return HTTPFound(location=model_url(self.context, request))
  198 +
  199 +The above handler does very little. Its *success* (represented by its
  200 +``__call__`` method) just redirects back to the base URL of the
  201 +context object.
  202 +
  203 +A handler needn't be a class. The above handler could be equivalently
  204 +written as a function:
  205 +
  206 +.. code-block:: python
  207 + :linenos:
  208 +
  209 + from webob.exc import HTTPFound
  210 + from repoze.bfg.url import model_url
  211 +
  212 + def Cancel(context, request, schema, form):
  213 + def success(converted):
  214 + return HTTPFound(location=model_url(context, request))
  215 + return success
  216 +
  217 +The return value of the above example's *success* is a "response"
  218 +object (an object which has the attributes ``app_iter``,
  219 +``headerlist`` and ``status``). A handler's *success* is permitted to
  220 +return a response or a dictionary. If it returns a dictionary, the
  221 +``template`` associated with the form is rendered with the result of
  222 +the dictionary in its global namespace. For example:
  223 +
  224 +.. code-block:: python
  225 + :linenos:
  226 +
  227 + from repoze.bfg.url import model_url
  228 + from api import TemplateAPI
  229 +
  230 + class Show(object):
  231 + def __init__(self, context, request, schema, form):
  232 + self.context = context
  233 + self.request = request
  234 + self.schema = schema
  235 + self.form = form
  236 +
  237 + def __call__(self, converted):
  238 + return {'api':TemplateAPI(self.context, self.request)}
  239 +
  240 +A *success* object may also raise a ``schemaish.Invalid`` exception if
  241 +it detects a post-validation error. This permits "whole-form"
  242 +validation that requires data that may only be known by the handler at
  243 +runtime.
  244 +
  245 +.. code-block:: python
  246 + :linenos:
  247 +
  248 + from repoze.bfg.url import model_url
  249 + from api import TemplateAPI
  250 + import schemaish
  251 +
  252 + class Show(object):
  253 + def __init__(self, context, request, schema, form):
  254 + self.context = context
  255 + self.request = request
  256 + self.schema = schema
  257 + self.form = form
  258 +
  259 + def __call__(self, converted):
  260 + raise schemaish.Invalid({'title':"I don't like this title"})
  261 +
  262 +When a success raises a schemaish.Invalid error, the form is
  263 +rerendered with the error present in the rendering.
  264 +
  265 +Influencing the Rendering of a Form With a Handler
  266 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  267 +
  268 +The examples of handlers we've seen so far do very little to influence
  269 +form rendering. By default, Formish will render a very basic default
  270 +form for a given schema, which is almost never adequate for real-world
  271 +use. Usually it is necessary to associate *defaults* and *widgets*
  272 +with a form. Here's a fairly complicated example that does just that:
  273 +
  274 +.. code-block:: python
  275 + :linenos:
  276 +
  277 + from repoze.bfg.url import model_url
  278 + from api import TemplateAPI
  279 + import security
  280 + import widgets
  281 + import formish
  282 +
  283 + class Add(object):
  284 + def __init__(self, context, request, schema, form):
  285 + defaults = {
  286 + 'title':'',
  287 + 'description':'',
  288 + 'text':'',
  289 + }
  290 +
  291 + form['title'].widget = formish.Input()
  292 + form['description'].widget = formish.TextArea(cols=60, rows=10)
  293 + form['text'].widget = widgets.RichTextWidget()
  294 +
  295 + workflow = security.get_workflow(context)
  296 +
  297 + if workflow is not None:
  298 + security_states = security.get_security_states(workflow, request)
  299 + if security_states:
  300 + field = schemaish.String(
  301 + description=('Private items can only be viewed by '
  302 + 'members of this community.'))
  303 + schema.attrs.insert(4, ('security_state', field))
  304 + defaults['security_state'] = workflow.initial_state
  305 + form['security_state'].widget = formish.RadioChoice(
  306 + options=[(s['name'],s['title']) for s in security_states],
  307 + none_option=None)
  308 +
  309 + form.defaults = defaults
  310 + self.context = context
  311 + self.request = request
  312 + self.workflow = workflow
  313 +
  314 + def __call__(self):
  315 + return {'api':TemplateAPI(self.context, self.request)}
  316 +
  317 +Note that the above example mutates both the *form* and the *schema*.
  318 +It adds *defaults* to the form, representing the data that will end up
  319 +in the widgets if the form is rendered "cleanly". It associates
  320 +widgets with particular form fields using the pattern ``form['name'] =
  321 +SomeWidget()``. This allows for customization of the form
  322 +presentation using Formish "widgets". It also mutates the schema,
  323 +adding an additional field to the schema if there is a valid
  324 +"workflow".
  325 +
  326 +The imports and associated APIs defined in this module are clearly
  327 +fictional, but for purposes of example, we'll assume that the
  328 +``security`` module offers an API which allows the developer to
  329 +determine whether a "workflow" is available for the current context
  330 +representing a dynamic set of choices based on the current state of
  331 +the context; furthermore it offers an API to see if there are any
  332 +valid security transitions for the current user associated with this
  333 +workflow. This sort of thing is typical in a content management
  334 +system. Although it is purely fictional, this example hopefully
  335 +demonstrates that we can influence both the form and the schema as
  336 +necessary based on a set of conditions in the handler's
  337 +initialization.
  338 +
  339 +The class name of the above example handler is ``Add``. This is
  340 +because it's meant to represent the handler of an action which is
  341 +invoked when a user submits an add form by clicking on the button that
  342 +represents a submit action title. Normally, such a handler would add
  343 +piece of content to the system. But currently it doesn't do anything
  344 +interesting on "success"; its ``__call__`` method returns a dictionary
  345 +and rerenders the template. We can change its ``__call__`` method to
  346 +do something more interesting when the form is submitted:
  347 +
  348 +.. code-block:: python
  349 + :linenos:
  350 +
  351 + from repoze.bfg.url import model_url
  352 + import content
  353 + import security
  354 + import formish
  355 + import schemaish
  356 + import widgets
  357 + from repoze.bfg.formish import ValidationError
  358 +
  359 + class Add(object):
  360 + def __init__(self, context, request, schema, form):
  361 +
  362 + defaults = {
  363 + 'title':'',
  364 + 'description':'',
  365 + 'text':'',
  366 + }
  367 +
  368 + form['title'].widget = formish.Input()
  369 + form['description'].widget = formish.TextArea(cols=60, rows=10)
  370 + form['text'].widget = widgets.RichTextWidget()
  371 +
  372 + workflow = security.get_workflow(context)
  373 + if workflow is not None:
  374 + security_states = security.get_security_states(workflow, request)
  375 + if security_states:
  376 + field = schemaish.String(
  377 + description=('Private items can only be viewed by '
  378 + 'members of this community.'))
  379 + schema.attrs.insert(4, ('security_state', field))
  380 + defaults['security_state'] = workflow.initial_state
  381 + form['security_state'].widget = formish.RadioChoice(
  382 + options=[(s['name'],s['title']) for s in security_states],
  383 + none_option=None)
  384 +
  385 + form.defaults = defaults
  386 + self.context = context
  387 + self.request = request
  388 + self.workflow = workflow
  389 +
  390 + def __call__(self, converted):
  391 + title = converted['title']
  392 + description = converted['description']
  393 + text = converted['text']
  394 + if title in self.context:
  395 + msg = '%s already exists in the context' % title
  396 + raise schemaish.Invalid({'title':msg})
  397 + entry = content.make_entry(title, description, text)
  398 + self.context[title] = entry
  399 + if self.workflow is not None:
  400 + if 'security_state' in converted:
  401 + self.workflow.transition(entry, self.request,
  402 + converted['security_state'])
  403 + location = model_url(self.context, self.request)
  404 + return HTTPFound(location=location)
  405 +
  406 +Indices and tables
  407 +------------------
  408 +
  409 +* :ref:`genindex`
  410 +* :ref:`modindex`
  411 +* :ref:`search`
276 ez_setup.py
... ... @@ -0,0 +1,276 @@
  1 +#!python
  2 +"""Bootstrap setuptools installation
  3 +
  4 +If you want to use setuptools in your package's setup.py, just include this
  5 +file in the same directory with it, and add this to the top of your setup.py::
  6 +
  7 + from ez_setup import use_setuptools
  8 + use_setuptools()
  9 +
  10 +If you want to require a specific version of setuptools, set a download
  11 +mirror, or use an alternate download directory, you can do so by supplying
  12 +the appropriate options to ``use_setuptools()``.
  13 +
  14 +This file can also be run as a script to install or upgrade setuptools.
  15 +"""
  16 +import sys
  17 +DEFAULT_VERSION = "0.6c9"
  18 +DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3]
  19 +
  20 +md5_data = {
  21 + 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
  22 + 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
  23 + 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
  24 + 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
  25 + 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
  26 + 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
  27 + 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
  28 + 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
  29 + 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
  30 + 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
  31 + 'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
  32 + 'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
  33 + 'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
  34 + 'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
  35 + 'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
  36 + 'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f',
  37 + 'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2',
  38 + 'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc',
  39 + 'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167',
  40 + 'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64',
  41 + 'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d',
  42 + 'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20',
  43 + 'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab',
  44 + 'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53',
  45 + 'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2',
  46 + 'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e',
  47 + 'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372',
  48 + 'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902',
  49 + 'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de',
  50 + 'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b',
  51 + 'setuptools-0.6c9-py2.3.egg': 'a83c4020414807b496e4cfbe08507c03',
  52 + 'setuptools-0.6c9-py2.4.egg': '260a2be2e5388d66bdaee06abec6342a',
  53 + 'setuptools-0.6c9-py2.5.egg': 'fe67c3e5a17b12c0e7c541b7ea43a8e6',
  54 + 'setuptools-0.6c9-py2.6.egg': 'ca37b1ff16fa2ede6e19383e7b59245a',
  55 +}
  56 +
  57 +import sys, os
  58 +try: from hashlib import md5
  59 +except ImportError: from md5 import md5
  60 +
  61 +def _validate_md5(egg_name, data):
  62 + if egg_name in md5_data:
  63 + digest = md5(data).hexdigest()
  64 + if digest != md5_data[egg_name]:
  65 + print >>sys.stderr, (
  66 + "md5 validation of %s failed! (Possible download problem?)"
  67 + % egg_name
  68 + )
  69 + sys.exit(2)
  70 + return data
  71 +
  72 +def use_setuptools(
  73 + version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
  74 + download_delay=15
  75 +):
  76 + """Automatically find/download setuptools and make it available on sys.path
  77 +
  78 + `version` should be a valid setuptools version number that is available
  79 + as an egg for download under the `download_base` URL (which should end with
  80 + a '/'). `to_dir` is the directory where setuptools will be downloaded, if
  81 + it is not already available. If `download_delay` is specified, it should
  82 + be the number of seconds that will be paused before initiating a download,
  83 + should one be required. If an older version of setuptools is installed,
  84 + this routine will print a message to ``sys.stderr`` and raise SystemExit in
  85 + an attempt to abort the calling script.
  86 + """
  87 + was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules
  88 + def do_download():
  89 + egg = download_setuptools(version, download_base, to_dir, download_delay)
  90 + sys.path.insert(0, egg)
  91 + import setuptools; setuptools.bootstrap_install_from = egg
  92 + try:
  93 + import pkg_resources
  94 + except ImportError:
  95 + return do_download()
  96 + try:
  97 + pkg_resources.require("setuptools>="+version); return
  98 + except pkg_resources.VersionConflict, e:
  99 + if was_imported:
  100 + print >>sys.stderr, (
  101 + "The required version of setuptools (>=%s) is not available, and\n"
  102 + "can't be installed while this script is running. Please install\n"
  103 + " a more recent version first, using 'easy_install -U setuptools'."
  104 + "\n\n(Currently using %r)"
  105 + ) % (version, e.args[0])
  106 + sys.exit(2)
  107 + else:
  108 + del pkg_resources, sys.modules['pkg_resources'] # reload ok
  109 + return do_download()
  110 + except pkg_resources.DistributionNotFound:
  111 + return do_download()
  112 +
  113 +def download_setuptools(
  114 + version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
  115 + delay = 15
  116 +):
  117 + """Download setuptools from a specified location and return its filename
  118 +
  119 + `version` should be a valid setuptools version number that is available
  120 + as an egg for download under the `download_base` URL (which should end
  121 + with a '/'). `to_dir` is the directory where the egg will be downloaded.
  122 + `delay` is the number of seconds to pause before an actual download attempt.
  123 + """
  124 + import urllib2, shutil
  125 + egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
  126 + url = download_base + egg_name
  127 + saveto = os.path.join(to_dir, egg_name)
  128 + src = dst = None
  129 + if not os.path.exists(saveto): # Avoid repeated downloads
  130 + try:
  131 + from distutils import log
  132 + if delay:
  133 + log.warn("""
  134 +---------------------------------------------------------------------------
  135 +This script requires setuptools version %s to run (even to display
  136 +help). I will attempt to download it for you (from
  137 +%s), but
  138 +you may need to enable firewall access for this script first.
  139 +I will start the download in %d seconds.
  140 +
  141 +(Note: if this machine does not have network access, please obtain the file
  142 +
  143 + %s
  144 +
  145 +and place it in this directory before rerunning this script.)
  146 +---------------------------------------------------------------------------""",
  147 + version, download_base, delay, url
  148 + ); from time import sleep; sleep(delay)
  149 + log.warn("Downloading %s", url)
  150 + src = urllib2.urlopen(url)
  151 + # Read/write all in one block, so we don't create a corrupt file
  152 + # if the download is interrupted.
  153 + data = _validate_md5(egg_name, src.read())
  154 + dst = open(saveto,"wb"); dst.write(data)
  155 + finally:
  156 + if src: src.close()
  157 + if dst: dst.close()
  158 + return os.path.realpath(saveto)
  159 +
  160 +
  161 +
  162 +
  163 +
  164 +
  165 +
  166 +
  167 +
  168 +
  169 +
  170 +
  171 +
  172 +
  173 +
  174 +
  175 +
  176 +
  177 +
  178 +
  179 +
  180 +
  181 +
  182 +
  183 +
  184 +
  185 +
  186 +
  187 +
  188 +
  189 +
  190 +
  191 +
  192 +
  193 +
  194 +
  195 +def main(argv, version=DEFAULT_VERSION):
  196 + """Install or upgrade setuptools and EasyInstall"""
  197 + try:
  198 + import setuptools
  199 + except ImportError:
  200 + egg = None
  201 + try:
  202 + egg = download_setuptools(version, delay=0)
  203 + sys.path.insert(0,egg)
  204 + from setuptools.command.easy_install import main
  205 + return main(list(argv)+[egg]) # we're done here
  206 + finally:
  207 + if egg and os.path.exists(egg):
  208 + os.unlink(egg)
  209 + else:
  210 + if setuptools.__version__ == '0.0.1':
  211 + print >>sys.stderr, (
  212 + "You have an obsolete version of setuptools installed. Please\n"
  213 + "remove it from your system entirely before rerunning this script."
  214 + )
  215 + sys.exit(2)
  216 +
  217 + req = "setuptools>="+version
  218 + import pkg_resources
  219 + try:
  220 + pkg_resources.require(req)
  221 + except pkg_resources.VersionConflict:
  222 + try:
  223 + from setuptools.command.easy_install import main
  224 + except ImportError:
  225 + from easy_install import main
  226 + main(list(argv)+[download_setuptools(delay=0)])
  227 + sys.exit(0) # try to force an exit
  228 + else:
  229 + if argv:
  230 + from setuptools.command.easy_install import main
  231 + main(argv)
  232 + else:
  233 + print "Setuptools version",version,"or greater has been installed."
  234 + print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
  235 +
  236 +def update_md5(filenames):
  237 + """Update our built-in md5 registry"""
  238 +
  239 + import re
  240 +
  241 + for name in filenames:
  242 + base = os.path.basename(name)
  243 + f = open(name,'rb')
  244 + md5_data[base] = md5(f.read()).hexdigest()
  245 + f.close()
  246 +
  247 + data = [" %r: %r,\n" % it for it in md5_data.items()]
  248 + data.sort()
  249 + repl = "".join(data)
  250 +
  251 + import inspect
  252 + srcfile = inspect.getsourcefile(sys.modules[__name__])
  253 + f = open(srcfile, 'rb'); src = f.read(); f.close()
  254 +
  255 + match = re.search("\nmd5_data = {\n([^}]+)}", src)
  256 + if not match:
  257 + print >>sys.stderr, "Internal error!"
  258 + sys.exit(2)
  259 +
  260 + src = src[:match.start(1)] + repl + src[match.end(1):]
  261 + f = open(srcfile,'w')
  262 + f.write(src)
  263 + f.close()
  264 +
  265 +
  266 +if __name__=='__main__':
  267 + if len(sys.argv)>2 and sys.argv[1]=='--md5update':
  268 + update_md5(sys.argv[2:])
  269 + else:
  270 + main(sys.argv[1:])
  271 +
  272 +
  273 +
  274 +
  275 +
  276 +
1  repoze/__init__.py
... ... @@ -0,0 +1 @@
  1 +__import__('pkg_resources').declare_namespace(__name__)
1  repoze/bfg/__init__.py
... ... @@ -0,0 +1 @@
  1 +__import__('pkg_resources').declare_namespace(__name__)
97 repoze/bfg/formish/__init__.py
... ... @@ -0,0 +1,97 @@
  1 +import os
  2 +import mako
  3 +
  4 +import formish
  5 +from pkg_resources import resource_filename
  6 +
  7 +from chameleon.zpt import language
  8 +from chameleon.zpt.template import PageTemplateFile
  9 +from zope.interface import Interface
  10 +from zope.component import queryUtility
  11 +from zope.component import getSiteManager
  12 +
  13 +from repoze.bfg.settings import get_settings
  14 +
  15 +def cache(func):
  16 + def load(self, *args):
  17 + template = self.registry.get(args)
  18 + if template is None:
  19 + self.registry[args] = template = func(self, *args)
  20 + return template
  21 + return load
  22 +
  23 +class IFormishSearchPath(Interface):
  24 + """ Utility interface representing a chameleon.formish search path """
  25 +
  26 +class IFormishRenderer(Interface):
  27 + """ Utility interface representing a formish renderer """
  28 +
  29 +class TemplateLoader(object):
  30 + parser = language.Parser()
  31 +
  32 + def __init__(self, search_path=None, auto_reload=False):
  33 + if search_path is None:
  34 + search_path = []
  35 + if isinstance(search_path, basestring):
  36 + search_path = [search_path]
  37 + self.search_path = search_path
  38 + self.auto_reload = auto_reload
  39 + self.registry = {}
  40 + self.notexists = {}
  41 +
  42 + @cache
  43 + def load(self, filename):
  44 + for path in self.search_path:
  45 + path = os.path.join(path, filename)
  46 + if path in self.notexists:
  47 + mako.exceptions.TopLevelLookupException(
  48 + "Can not find template %s" % filename)
  49 + try:
  50 + return PageTemplateFile(path, parser=self.parser,
  51 + auto_reload=self.auto_reload)
  52 + except OSError, e:
  53 + self.notexists[path] = True
  54 +
  55 + raise mako.exceptions.TopLevelLookupException(
  56 + "Can not find template %s" % filename)
  57 +
  58 +class ZPTRenderer(object):
  59 + def __init__(self, directories=None):
  60 + settings = get_settings()
  61 + auto_reload = settings and settings['auto_reload'] or False
  62 + if directories is None:
  63 + directories = []
  64 + if isinstance(directories, basestring):
  65 + directories = [directories]
  66 + self.directories = list(directories)
  67 + # if there are ZCML-registered directories, use those too
  68 + more = queryUtility(IFormishSearchPath, default=[])
  69 + directories.extend(more)
  70 + default = resource_filename('repoze.bfg.formish', 'templates/zpt')
  71 + directories.append(default)
  72 + self.loader = TemplateLoader(directories, auto_reload=auto_reload)
  73 +
  74 + def __call__(self, template, args):
  75 + if template.startswith('/'):
  76 + template = template[1:]
  77 + template = self.loader.load(template)
  78 + return template(**args)
  79 +
  80 +def get_default_renderer():
  81 + sm = getSiteManager()
  82 + renderer = queryUtility(IFormishRenderer)
  83 + if renderer is None:
  84 + # register a default renderer
  85 + renderer = ZPTRenderer()
  86 + sm.registerUtility(renderer, IFormishRenderer)
  87 + return renderer
  88 +
  89 +class Form(formish.Form):
  90 + def __init__(self, *arg, **kw):
  91 + if not 'renderer' in kw:
  92 + kw['renderer'] = get_default_renderer() # need to defer this til now
  93 + formish.Form.__init__(self, *arg, **kw)
  94 +
  95 +
  96 +
  97 +
28 repoze/bfg/formish/meta.zcml
... ... @@ -0,0 +1,28 @@
  1 +<configure
  2 + xmlns="http://namespaces.zope.org/zope"
  3 + xmlns:meta="http://namespaces.zope.org/meta">
  4 +
  5 + <meta:groupingDirective
  6 + name="form"
  7 + namespace="http://namespaces.repoze.org/formish"
  8 + schema="repoze.bfg.formish.zcml.IFormDirective"
  9 + handler="repoze.bfg.zcml.FormDirective"
  10 + />
  11 +
  12 + <meta:directive
  13 + name="action"
  14 + namespace="http://namespaces.repoze.org/formish"
  15 + usedIn="repoze.workflow.zcml.IWorkflowDirective"
  16 + schema="repoze.workflow.zcml.IActionDirective"
  17 + handler="repoze.workflow.zcml.ActionDirective"
  18 + />
  19 +
  20 + <meta:directive
  21 + name="add_template_path"
  22 + namespace="http://namespaces.repoze.org/formish"
  23 + schema="repoze.bfg.formish.zcml.IAddTemplatePath"
  24 + handler="repoze.bfg.formish.zcml.add_template_path"
  25 + />
  26 +
  27 +</configure>
  28 +
148 repoze/bfg/formish/zcml.py
... ... @@ -0,0 +1,148 @@
  1 +import os
  2 +from pkg_resources import resource_filename
  3 +
  4 +from formish import validation
  5 +
  6 +from zope.component import getSiteManager
  7 +import zope.configuration.config
  8 +from zope.configuration.fields import GlobalObject
  9 +
  10 +from zope.interface import Interface
  11 +from zope.interface import implements
  12 +
  13 +from zope.schema import TextLine
  14 +
  15 +from repoze.bfg.zcml import view
  16 +
  17 +from repoze.bfg.formish import Form
  18 +from repoze.bfg.formish import IFormishSearchPath
  19 +
  20 +class IFormDirective(Interface):
  21 + schema = GlobalObject(title=u'schema', required=True)
  22 + renderer = TextLine(title=u'renderer', required=True)
  23 + for_ = GlobalObject(title=u'for', required=False)
  24 + name = TextLine(title=u'name', required=False)
  25 + template = TextLine(title=u'template', required=False)
  26 + permission = TextLine(title=u'permission', required=False)
  27 + containment = GlobalObject(title=u'containment', required=False)
  28 + route_name = TextLine(title=u'route_name', required=False)
  29 + wrapper = TextLine(title = u'wrapper', required=False)
  30 +
  31 +class FormDirective(zope.configuration.config.GroupingContextDecorator):
  32 + implements(zope.configuration.config.IConfigurationContext,
  33 + IFormDirective)
  34 + def __init__(self, context, schema, renderer, for_=None, name='',
  35 + template=None, permission=None, containment=None,
  36 + route_name=None, wrapper=None):
  37 + self.context = context
  38 + self.schema = schema