Skip to content
Browse files

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

  • Loading branch information...
0 parents commit 6c3a2b94024e399692fa8822be4d69b1d5ee8279 Chris McDonough committed Sep 24, 2009
7 CHANGES.txt
@@ -0,0 +1,7 @@
+repoze.atemplate Changelog
+==========================
+
+0.1 (unreleaesd)
+----------------
+
+- Initial release.
3 COPYRIGHT.txt
@@ -0,0 +1,3 @@
+Copyright (c) 2008 Agendaless Consulting and Contributors.
+(http://www.agendaless.com), All Rights Reserved
+
41 LICENSE.txt
@@ -0,0 +1,41 @@
+License
+
+ A copyright notice accompanies this license document that identifies
+ the copyright holders.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ 1. Redistributions in source code must retain the accompanying
+ copyright notice, this list of conditions, and the following
+ disclaimer.
+
+ 2. Redistributions in binary form must reproduce the accompanying
+ copyright notice, this list of conditions, and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+ 3. Names of the copyright holders must not be used to endorse or
+ promote products derived from this software without prior
+ written permission from the copyright holders.
+
+ 4. If any files are modified, you must cause the modified files to
+ carry prominent notices stating that you changed the files and
+ the date of any change.
+
+ Disclaimer
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND
+ ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGE.
+
4 README.txt
@@ -0,0 +1,4 @@
+repoze.atemplate README
+=======================
+
+Please see docs/index.rst for the documentation.
BIN docs/.static/logo_hi.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 docs/.static/repoze.css
@@ -0,0 +1,22 @@
+@import url('default.css');
+body {
+ background-color: #006339;
+}
+
+div.document {
+ background-color: #dad3bd;
+}
+
+div.sphinxsidebar h3, h4, h5, a {
+ color: #127c56 !important;
+}
+
+div.related {
+ color: #dad3bd !important;
+ background-color: #00744a;
+}
+
+div.related a {
+ color: #dad3bd !important;
+}
+
70 docs/Makefile
@@ -0,0 +1,70 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d .build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html web pickle htmlhelp latex changes linkcheck
+
+help:
+ @echo "Please use \`make <target>' where <target> is one of"
+ @echo " html to make standalone HTML files"
+ @echo " pickle to make pickle files (usable by e.g. sphinx-web)"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " changes to make an overview over all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+
+clean:
+ -rm -rf .build/*
+
+html:
+ mkdir -p .build/html .build/doctrees
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) .build/html
+ @echo
+ @echo "Build finished. The HTML pages are in .build/html."
+
+pickle:
+ mkdir -p .build/pickle .build/doctrees
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) .build/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files or run"
+ @echo " sphinx-web .build/pickle"
+ @echo "to start the sphinx-web server."
+
+web: pickle
+
+htmlhelp:
+ mkdir -p .build/htmlhelp .build/doctrees
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) .build/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in .build/htmlhelp."
+
+latex:
+ mkdir -p .build/latex .build/doctrees
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) .build/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in .build/latex."
+ @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
+ "run these through (pdf)latex."
+
+changes:
+ mkdir -p .build/changes .build/doctrees
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) .build/changes
+ @echo
+ @echo "The overview file is in .build/changes."
+
+linkcheck:
+ mkdir -p .build/linkcheck .build/doctrees
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) .build/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in .build/linkcheck/output.txt."
14 docs/api.rst
@@ -0,0 +1,14 @@
+API Documentation for repoze.atemplate
+======================================
+
+Interfaces
+----------
+
+Document interfaces here. You may want to use the ``.. autointerface::``
+directive provided by :mod:`repoze.sphinx.autointerface`, in which case
+you will need to add it to the extensions in `conf.py`.
+
+Exceptions
+----------
+
+Document any custom exceptions here.
185 docs/conf.py
@@ -0,0 +1,185 @@
+# -*- coding: utf-8 -*-
+#
+# repoze.bfg.formish documentation build configuration file
+#
+# This file is execfile()d with the current directory set to its containing
+# dir.
+#
+# The contents of this file are pickled, so don't put values in the
+# namespace that aren't pickleable (module imports are okay, they're
+# removed automatically).
+#
+# All configuration values have a default value; values that are commented
+# out serve to show the default value.
+
+import sys, os
+
+# If your extensions are in another directory, add it here. If the
+# directory is relative to the documentation root, use os.path.abspath to
+# make it absolute, like shown here.
+#sys.path.append(os.path.abspath('some/directory'))
+
+# General configuration
+# ---------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['.templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General substitutions.
+project = 'repoze.bfg.formish'
+copyright = '2009, Repoze Developers <repoze-dev@lists.repoze.org>'
+
+# The default replacements for |version| and |release|, also used in various
+# other places throughout the built documents.
+#
+# The short X.Y version.
+version = '0.1'
+# The full version, including alpha/beta/rc tags.
+release = '0.1'
+
+# There are two options for replacing |today|: either, you set today to
+# some non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+
+# List of directories, relative to source directories, that shouldn't be
+# searched for source files.
+#exclude_dirs = []
+
+# The reST default role (used for this markup: `text`) to use for all
+# documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+
+# Options for HTML output
+# -----------------------
+
+# The style sheet to use for HTML and HTML Help pages. A file of that name
+# must exist either in Sphinx' static/ path, or in one of the custom paths
+# given in html_static_path.
+html_style = 'repoze.css'
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar. Default is the same as
+# html_title.
+#html_short_title = None
+
+# The name of an image file (within the static path) to place at the top of
+# the sidebar.
+html_logo = '.static/logo_hi.gif'
+
+# The name of an image file (within the static path) to use as favicon of
+# the docs. This file should be a Windows icon file (.ico) being 16x16 or
+# 32x32 pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets)
+# here, relative to this directory. They are copied after the builtin
+# static files, so a file named "default.css" will overwrite the builtin
+# "default.css".
+html_static_path = ['.static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page
+# bottom, using the given strftime format.
+html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_use_modindex = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, the reST sources are included in the HTML build as
+# _sources/<name>.
+#html_copy_source = True
+
+# If true, an OpenSearch description file will be output, and all pages
+# will contain a <link> tag referring to it. The value of this option must
+# be the base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'atemplatedoc'
+
+
+# Options for LaTeX output
+# ------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, document class [howto/manual]).
+latex_documents = [
+ ('index', 'rbfgformish.tex', 'repoze.bfg.formish Documentation',
+ 'Repoze Developers', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the
+# top of the title page.
+latex_logo = '.static/logo_hi.gif'
+
+# For "manual" documents, if this is true, then toplevel headings are
+# parts, not chapters.
+#latex_use_parts = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_use_modindex = True
411 docs/index.rst
@@ -0,0 +1,411 @@
+Documentation for repoze.bfg.formish
+====================================
+
+:mod:`repoze.bfg.formish` is a package which provides
+:mod:`repoze.bfg` bindings for the :term:`Formish` package, which is
+an excellent form generation and validation package for Python.
+
+This package provides:
+
+- A ``chameleon.zpt`` Formish "renderer" class.
+
+- A set of ``chameleon.zpt`` templates which implement the default
+ Formish widgetset.
+
+- A convenience ``Form`` implementation, which inherits from the
+ standard ``formish.Form`` class, but uses the ``chameleon_zpt``
+ renderer instead of the default Formish ``mako`` renderer.
+
+- A ``formish:add_template_path`` ZCML directive which allows for the
+ configuration of override Formish template locations.
+
+- A ``formish:form`` ZCML directive which can be used to configure a
+ Formish form "semi-declaratively".
+
+Schemas
+-------
+
+Schemas are :term:`Schemaish` schema definitions, usually defined at
+module scope as a class that inherits from the class
+``schemaish.Structure``. For example:
+
+.. code-block:: python
+ :linenos:
+
+ import schemaish
+ from validatish import validator
+
+ class AddCommunitySchema(schemaish.Structure):
+ title = schemaish.String(validator=validator.Required())
+ description = schemaish.String(
+ description = ('This description will appear in search results and '
+ 'on the community listing page. Please limit your '
+ ' description to 100 words or less'),
+ validator = validator.All(validator.Length(max=500),
+ validator.Required())
+ )
+ text = schemaish.String(
+ description = ('This text will appear on the Overview page for this '
+ 'community. You can use this to describe the '
+ 'community or to make a special announcement.'))
+ tools = schemaish.Sequence(
+ attr = schemaish.String(),
+ description = 'Select which tools to enable on this community.')
+
+A schema describes the data types of fields associated with a form, as
+well as any validation constraints for individual fields on the form.
+A schema does not describe the user interface elements associated with
+the fields it describes. Schemas are consulted by *forms*, which are
+separate entities in the :term:`Formish` design. A form is
+responsible for describing the user interface associated with a
+schema.
+
+``formish:form`` ZCML Directive
+-------------------------------
+
+When the ``meta.zcml`` of :mod:`repoze.bfg.formish` is included
+within your :mod:`repoze.bfg` application, you can make use of the
+``formish:form`` ZCML directive. The ZCML directive uses two major
+concepts: schemas and actions.
+
+You must add the following to your application's ``configure.zcml`` to
+use the ``formish:form`` directive:
+
+.. code-block:: xml
+ :linenos:
+
+ <include package="repoze.bfg.formish" file="meta.zcml"/>
+
+You refer to the schema defined in a Python within a ``formish:form``
+directive using the ``schema`` attribute, which is a dotted Python
+name referring to the schema class:
+
+.. code-block:: xml
+ :linenos:
+
+ <formish:form
+ for=".models.MyModel"
+ name="add_community.html"
+ schema=".forms.AddCommunitySchema"
+ template="templates/form_template.pt"
+ handler=".forms.Show"/>
+
+A ``formish:form`` configures one or more special :mod:`repoze.bfg`
+"view" callables that render a form.
+
+The ``for`` and ``name`` attributes of a ``formish:form`` tag mirror
+the meaning of the meanings of these names in :mod:`repoze.bfg`
+``view`` ZCML directive. ``for`` represents the class or interface
+which the context must implement for this view to be invoked.
+``name`` is the view name.
+
+The above example assumes that there is a ``forms`` module which lives
+in the same directory as the ``configure.zcml`` of your application,
+and that it contains a schema definition named ``AddCommunitySchema``.
+This is the value represented by the ``schema`` attribute above.
+
+It also names a Chameleon ZPT template via its ``template`` attribute
+which will be used to render the form when it is first presented, or
+when form validation fails. The template is either a BFG "resource
+specification" or an absolute or ZCML-package-relative path to an
+on-disk template.
+
+The ``handler`` attribute names a "default" *handler* for this form
+definition. The definition of a *handler* is discussed below; for now
+please assume it is a dotted Python name which specifies a special
+kind of callable. The above example names it as ``.forms.Show``,
+which makes the assumption that a handler callable named ``Show``
+lives in the package containing the ``configure.zcml`` file in a
+module named ``forms``.
+
+Actions
+-------
+
+An *action* is a subdirective of the ``formish:form`` directive. It
+names a *handler*, a *param*, and a *title*. For example:
+
+.. code-block:: xml
+ :linenos:
+
+ <formish:form
+ for=".models.MyModel"
+ name="add_community.html"
+ schema=".forms.AddCommunitySchema"
+ template="templates/form_template.pt"
+ handler=".forms.Show">
+
+ <action
+ handler=".forms.Cancel"
+ param="form_cancel"
+ title="Cancel"
+ />
+
+ </formish:form>
+
+Any number of ``action`` tags can be present within a ``formish:form``
+tag.
+
+Each ``action`` tag represents a submit button at the bottom of the
+form that will be given an HTML "value" matching the ``param``
+attribute. When this button is pressed, the value of ``param`` will
+be present in the ``request.params`` dictionary. The *value* of the
+button (the text visible to the user) will be the value of the
+``title`` attribute.
+
+Each action additionally must specify a ``handler`` attribute, which
+is the dotted Python to a *factory* which has the capability to
+influence the painting of the form as well as what happens when form
+validation succeeds. A particular handler is invoked only when the
+value of the ``param`` attribute for its action is present as a key in
+the ``request.params`` dictionary.
+
+If there is no key in in ``request.params`` dictionary which matches
+the ``param`` of a particular form's action, the handler of the form
+itself is called. For example, if the form we're defining above is
+invoked with a request has a params dict that has the value
+``form_cancel``, the ``.forms.Cancel`` handler is called. But if
+``form_cancel`` is not present, the ``.forms.Show`` handler is called.
+
+Handlers
+--------
+
+A :term:`handler` is the dotted name to a Python callable which must
+accept four arguments: ``context``, ``request``, ``schema`` and
+``form``. It must *return* a callable object which must accept a
+single argument: ``converted``, which will be a dictionary represented
+the values present in the form post when form validation was
+successful. The callable object which a handler returns is called
+when form validation succeeds. It is known as the *success* or
+*success callable*. A handler is usually represented a class, with
+its ``__init__`` method representing the initialization, and its
+``__call__`` method representing the success callable. For example:
+
+.. code-block:: python
+ :linenos:
+
+ from webob.exc import HTTPFound
+ from repoze.bfg.url import model_url
+
+ class Cancel(object):
+ def __init__(self, context, request, schema, form):
+ self.context = context
+ self.request = request
+ self.schema = schema
+ self.form = form
+
+ def __call__(self, converted):
+ return HTTPFound(location=model_url(self.context, request))
+
+The above handler does very little. Its *success* (represented by its
+``__call__`` method) just redirects back to the base URL of the
+context object.
+
+A handler needn't be a class. The above handler could be equivalently
+written as a function:
+
+.. code-block:: python
+ :linenos:
+
+ from webob.exc import HTTPFound
+ from repoze.bfg.url import model_url
+
+ def Cancel(context, request, schema, form):
+ def success(converted):
+ return HTTPFound(location=model_url(context, request))
+ return success
+
+The return value of the above example's *success* is a "response"
+object (an object which has the attributes ``app_iter``,
+``headerlist`` and ``status``). A handler's *success* is permitted to
+return a response or a dictionary. If it returns a dictionary, the
+``template`` associated with the form is rendered with the result of
+the dictionary in its global namespace. For example:
+
+.. code-block:: python
+ :linenos:
+
+ from repoze.bfg.url import model_url
+ from api import TemplateAPI
+
+ class Show(object):
+ def __init__(self, context, request, schema, form):
+ self.context = context
+ self.request = request
+ self.schema = schema
+ self.form = form
+
+ def __call__(self, converted):
+ return {'api':TemplateAPI(self.context, self.request)}
+
+A *success* object may also raise a ``schemaish.Invalid`` exception if
+it detects a post-validation error. This permits "whole-form"
+validation that requires data that may only be known by the handler at
+runtime.
+
+.. code-block:: python
+ :linenos:
+
+ from repoze.bfg.url import model_url
+ from api import TemplateAPI
+ import schemaish
+
+ class Show(object):
+ def __init__(self, context, request, schema, form):
+ self.context = context
+ self.request = request
+ self.schema = schema
+ self.form = form
+
+ def __call__(self, converted):
+ raise schemaish.Invalid({'title':"I don't like this title"})
+
+When a success raises a schemaish.Invalid error, the form is
+rerendered with the error present in the rendering.
+
+Influencing the Rendering of a Form With a Handler
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The examples of handlers we've seen so far do very little to influence
+form rendering. By default, Formish will render a very basic default
+form for a given schema, which is almost never adequate for real-world
+use. Usually it is necessary to associate *defaults* and *widgets*
+with a form. Here's a fairly complicated example that does just that:
+
+.. code-block:: python
+ :linenos:
+
+ from repoze.bfg.url import model_url
+ from api import TemplateAPI
+ import security
+ import widgets
+ import formish
+
+ class Add(object):
+ def __init__(self, context, request, schema, form):
+ defaults = {
+ 'title':'',
+ 'description':'',
+ 'text':'',
+ }
+
+ form['title'].widget = formish.Input()
+ form['description'].widget = formish.TextArea(cols=60, rows=10)
+ form['text'].widget = widgets.RichTextWidget()
+
+ workflow = security.get_workflow(context)
+
+ if workflow is not None:
+ security_states = security.get_security_states(workflow, request)
+ if security_states:
+ field = schemaish.String(
+ description=('Private items can only be viewed by '
+ 'members of this community.'))
+ schema.attrs.insert(4, ('security_state', field))
+ defaults['security_state'] = workflow.initial_state
+ form['security_state'].widget = formish.RadioChoice(
+ options=[(s['name'],s['title']) for s in security_states],
+ none_option=None)
+
+ form.defaults = defaults
+ self.context = context
+ self.request = request
+ self.workflow = workflow
+
+ def __call__(self):
+ return {'api':TemplateAPI(self.context, self.request)}
+
+Note that the above example mutates both the *form* and the *schema*.
+It adds *defaults* to the form, representing the data that will end up
+in the widgets if the form is rendered "cleanly". It associates
+widgets with particular form fields using the pattern ``form['name'] =
+SomeWidget()``. This allows for customization of the form
+presentation using Formish "widgets". It also mutates the schema,
+adding an additional field to the schema if there is a valid
+"workflow".
+
+The imports and associated APIs defined in this module are clearly
+fictional, but for purposes of example, we'll assume that the
+``security`` module offers an API which allows the developer to
+determine whether a "workflow" is available for the current context
+representing a dynamic set of choices based on the current state of
+the context; furthermore it offers an API to see if there are any
+valid security transitions for the current user associated with this
+workflow. This sort of thing is typical in a content management
+system. Although it is purely fictional, this example hopefully
+demonstrates that we can influence both the form and the schema as
+necessary based on a set of conditions in the handler's
+initialization.
+
+The class name of the above example handler is ``Add``. This is
+because it's meant to represent the handler of an action which is
+invoked when a user submits an add form by clicking on the button that
+represents a submit action title. Normally, such a handler would add
+piece of content to the system. But currently it doesn't do anything
+interesting on "success"; its ``__call__`` method returns a dictionary
+and rerenders the template. We can change its ``__call__`` method to
+do something more interesting when the form is submitted:
+
+.. code-block:: python
+ :linenos:
+
+ from repoze.bfg.url import model_url
+ import content
+ import security
+ import formish
+ import schemaish
+ import widgets
+ from repoze.bfg.formish import ValidationError
+
+ class Add(object):
+ def __init__(self, context, request, schema, form):
+
+ defaults = {
+ 'title':'',
+ 'description':'',
+ 'text':'',
+ }
+
+ form['title'].widget = formish.Input()
+ form['description'].widget = formish.TextArea(cols=60, rows=10)
+ form['text'].widget = widgets.RichTextWidget()
+
+ workflow = security.get_workflow(context)
+ if workflow is not None:
+ security_states = security.get_security_states(workflow, request)
+ if security_states:
+ field = schemaish.String(
+ description=('Private items can only be viewed by '
+ 'members of this community.'))
+ schema.attrs.insert(4, ('security_state', field))
+ defaults['security_state'] = workflow.initial_state
+ form['security_state'].widget = formish.RadioChoice(
+ options=[(s['name'],s['title']) for s in security_states],
+ none_option=None)
+
+ form.defaults = defaults
+ self.context = context
+ self.request = request
+ self.workflow = workflow
+
+ def __call__(self, converted):
+ title = converted['title']
+ description = converted['description']
+ text = converted['text']
+ if title in self.context:
+ msg = '%s already exists in the context' % title
+ raise schemaish.Invalid({'title':msg})
+ entry = content.make_entry(title, description, text)
+ self.context[title] = entry
+ if self.workflow is not None:
+ if 'security_state' in converted:
+ self.workflow.transition(entry, self.request,
+ converted['security_state'])
+ location = model_url(self.context, self.request)
+ return HTTPFound(location=location)
+
+Indices and tables
+------------------
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
276 ez_setup.py
@@ -0,0 +1,276 @@
+#!python
+"""Bootstrap setuptools installation
+
+If you want to use setuptools in your package's setup.py, just include this
+file in the same directory with it, and add this to the top of your setup.py::
+
+ from ez_setup import use_setuptools
+ use_setuptools()
+
+If you want to require a specific version of setuptools, set a download
+mirror, or use an alternate download directory, you can do so by supplying
+the appropriate options to ``use_setuptools()``.
+
+This file can also be run as a script to install or upgrade setuptools.
+"""
+import sys
+DEFAULT_VERSION = "0.6c9"
+DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3]
+
+md5_data = {
+ 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
+ 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
+ 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
+ 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
+ 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
+ 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
+ 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
+ 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
+ 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
+ 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
+ 'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
+ 'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
+ 'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
+ 'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
+ 'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
+ 'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f',
+ 'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2',
+ 'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc',
+ 'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167',
+ 'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64',
+ 'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d',
+ 'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20',
+ 'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab',
+ 'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53',
+ 'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2',
+ 'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e',
+ 'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372',
+ 'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902',
+ 'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de',
+ 'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b',
+ 'setuptools-0.6c9-py2.3.egg': 'a83c4020414807b496e4cfbe08507c03',
+ 'setuptools-0.6c9-py2.4.egg': '260a2be2e5388d66bdaee06abec6342a',
+ 'setuptools-0.6c9-py2.5.egg': 'fe67c3e5a17b12c0e7c541b7ea43a8e6',
+ 'setuptools-0.6c9-py2.6.egg': 'ca37b1ff16fa2ede6e19383e7b59245a',
+}
+
+import sys, os
+try: from hashlib import md5
+except ImportError: from md5 import md5
+
+def _validate_md5(egg_name, data):
+ if egg_name in md5_data:
+ digest = md5(data).hexdigest()
+ if digest != md5_data[egg_name]:
+ print >>sys.stderr, (
+ "md5 validation of %s failed! (Possible download problem?)"
+ % egg_name
+ )
+ sys.exit(2)
+ return data
+
+def use_setuptools(
+ version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
+ download_delay=15
+):
+ """Automatically find/download setuptools and make it available on sys.path
+
+ `version` should be a valid setuptools version number that is available
+ as an egg for download under the `download_base` URL (which should end with
+ a '/'). `to_dir` is the directory where setuptools will be downloaded, if
+ it is not already available. If `download_delay` is specified, it should
+ be the number of seconds that will be paused before initiating a download,
+ should one be required. If an older version of setuptools is installed,
+ this routine will print a message to ``sys.stderr`` and raise SystemExit in
+ an attempt to abort the calling script.
+ """
+ was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules
+ def do_download():
+ egg = download_setuptools(version, download_base, to_dir, download_delay)
+ sys.path.insert(0, egg)
+ import setuptools; setuptools.bootstrap_install_from = egg
+ try:
+ import pkg_resources
+ except ImportError:
+ return do_download()
+ try:
+ pkg_resources.require("setuptools>="+version); return
+ except pkg_resources.VersionConflict, e:
+ if was_imported:
+ print >>sys.stderr, (
+ "The required version of setuptools (>=%s) is not available, and\n"
+ "can't be installed while this script is running. Please install\n"
+ " a more recent version first, using 'easy_install -U setuptools'."
+ "\n\n(Currently using %r)"
+ ) % (version, e.args[0])
+ sys.exit(2)
+ else:
+ del pkg_resources, sys.modules['pkg_resources'] # reload ok
+ return do_download()
+ except pkg_resources.DistributionNotFound:
+ return do_download()
+
+def download_setuptools(
+ version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
+ delay = 15
+):
+ """Download setuptools from a specified location and return its filename
+
+ `version` should be a valid setuptools version number that is available
+ as an egg for download under the `download_base` URL (which should end
+ with a '/'). `to_dir` is the directory where the egg will be downloaded.
+ `delay` is the number of seconds to pause before an actual download attempt.
+ """
+ import urllib2, shutil
+ egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
+ url = download_base + egg_name
+ saveto = os.path.join(to_dir, egg_name)
+ src = dst = None
+ if not os.path.exists(saveto): # Avoid repeated downloads
+ try:
+ from distutils import log
+ if delay:
+ log.warn("""
+---------------------------------------------------------------------------
+This script requires setuptools version %s to run (even to display
+help). I will attempt to download it for you (from
+%s), but
+you may need to enable firewall access for this script first.
+I will start the download in %d seconds.
+
+(Note: if this machine does not have network access, please obtain the file
+
+ %s
+
+and place it in this directory before rerunning this script.)
+---------------------------------------------------------------------------""",
+ version, download_base, delay, url
+ ); from time import sleep; sleep(delay)
+ log.warn("Downloading %s", url)
+ src = urllib2.urlopen(url)
+ # Read/write all in one block, so we don't create a corrupt file
+ # if the download is interrupted.
+ data = _validate_md5(egg_name, src.read())
+ dst = open(saveto,"wb"); dst.write(data)
+ finally:
+ if src: src.close()
+ if dst: dst.close()
+ return os.path.realpath(saveto)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+def main(argv, version=DEFAULT_VERSION):
+ """Install or upgrade setuptools and EasyInstall"""
+ try:
+ import setuptools
+ except ImportError:
+ egg = None
+ try:
+ egg = download_setuptools(version, delay=0)
+ sys.path.insert(0,egg)
+ from setuptools.command.easy_install import main
+ return main(list(argv)+[egg]) # we're done here
+ finally:
+ if egg and os.path.exists(egg):
+ os.unlink(egg)
+ else:
+ if setuptools.__version__ == '0.0.1':
+ print >>sys.stderr, (
+ "You have an obsolete version of setuptools installed. Please\n"
+ "remove it from your system entirely before rerunning this script."
+ )
+ sys.exit(2)
+
+ req = "setuptools>="+version
+ import pkg_resources
+ try:
+ pkg_resources.require(req)
+ except pkg_resources.VersionConflict:
+ try:
+ from setuptools.command.easy_install import main
+ except ImportError:
+ from easy_install import main
+ main(list(argv)+[download_setuptools(delay=0)])
+ sys.exit(0) # try to force an exit
+ else:
+ if argv:
+ from setuptools.command.easy_install import main
+ main(argv)
+ else:
+ print "Setuptools version",version,"or greater has been installed."
+ print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
+
+def update_md5(filenames):
+ """Update our built-in md5 registry"""
+
+ import re
+
+ for name in filenames:
+ base = os.path.basename(name)
+ f = open(name,'rb')
+ md5_data[base] = md5(f.read()).hexdigest()
+ f.close()
+
+ data = [" %r: %r,\n" % it for it in md5_data.items()]
+ data.sort()
+ repl = "".join(data)
+
+ import inspect
+ srcfile = inspect.getsourcefile(sys.modules[__name__])
+ f = open(srcfile, 'rb'); src = f.read(); f.close()
+
+ match = re.search("\nmd5_data = {\n([^}]+)}", src)
+ if not match:
+ print >>sys.stderr, "Internal error!"
+ sys.exit(2)
+
+ src = src[:match.start(1)] + repl + src[match.end(1):]
+ f = open(srcfile,'w')
+ f.write(src)
+ f.close()
+
+
+if __name__=='__main__':
+ if len(sys.argv)>2 and sys.argv[1]=='--md5update':
+ update_md5(sys.argv[2:])
+ else:
+ main(sys.argv[1:])
+
+
+
+
+
+
1 repoze/__init__.py
@@ -0,0 +1 @@
+__import__('pkg_resources').declare_namespace(__name__)
1 repoze/bfg/__init__.py
@@ -0,0 +1 @@
+__import__('pkg_resources').declare_namespace(__name__)
97 repoze/bfg/formish/__init__.py
@@ -0,0 +1,97 @@
+import os
+import mako
+
+import formish
+from pkg_resources import resource_filename
+
+from chameleon.zpt import language
+from chameleon.zpt.template import PageTemplateFile
+from zope.interface import Interface
+from zope.component import queryUtility
+from zope.component import getSiteManager
+
+from repoze.bfg.settings import get_settings
+
+def cache(func):
+ def load(self, *args):
+ template = self.registry.get(args)
+ if template is None:
+ self.registry[args] = template = func(self, *args)
+ return template
+ return load
+
+class IFormishSearchPath(Interface):
+ """ Utility interface representing a chameleon.formish search path """
+
+class IFormishRenderer(Interface):
+ """ Utility interface representing a formish renderer """
+
+class TemplateLoader(object):
+ parser = language.Parser()
+
+ def __init__(self, search_path=None, auto_reload=False):
+ if search_path is None:
+ search_path = []
+ if isinstance(search_path, basestring):
+ search_path = [search_path]
+ self.search_path = search_path
+ self.auto_reload = auto_reload
+ self.registry = {}
+ self.notexists = {}
+
+ @cache
+ def load(self, filename):
+ for path in self.search_path:
+ path = os.path.join(path, filename)
+ if path in self.notexists:
+ mako.exceptions.TopLevelLookupException(
+ "Can not find template %s" % filename)
+ try:
+ return PageTemplateFile(path, parser=self.parser,
+ auto_reload=self.auto_reload)
+ except OSError, e:
+ self.notexists[path] = True
+
+ raise mako.exceptions.TopLevelLookupException(
+ "Can not find template %s" % filename)
+
+class ZPTRenderer(object):
+ def __init__(self, directories=None):
+ settings = get_settings()
+ auto_reload = settings and settings['auto_reload'] or False
+ if directories is None:
+ directories = []
+ if isinstance(directories, basestring):
+ directories = [directories]
+ self.directories = list(directories)
+ # if there are ZCML-registered directories, use those too
+ more = queryUtility(IFormishSearchPath, default=[])
+ directories.extend(more)
+ default = resource_filename('repoze.bfg.formish', 'templates/zpt')
+ directories.append(default)
+ self.loader = TemplateLoader(directories, auto_reload=auto_reload)
+
+ def __call__(self, template, args):
+ if template.startswith('/'):
+ template = template[1:]
+ template = self.loader.load(template)
+ return template(**args)
+
+def get_default_renderer():
+ sm = getSiteManager()
+ renderer = queryUtility(IFormishRenderer)
+ if renderer is None:
+ # register a default renderer
+ renderer = ZPTRenderer()
+ sm.registerUtility(renderer, IFormishRenderer)
+ return renderer
+
+class Form(formish.Form):
+ def __init__(self, *arg, **kw):
+ if not 'renderer' in kw:
+ kw['renderer'] = get_default_renderer() # need to defer this til now
+ formish.Form.__init__(self, *arg, **kw)
+
+
+
+
28 repoze/bfg/formish/meta.zcml
@@ -0,0 +1,28 @@
+<configure
+ xmlns="http://namespaces.zope.org/zope"
+ xmlns:meta="http://namespaces.zope.org/meta">
+
+ <meta:groupingDirective
+ name="form"
+ namespace="http://namespaces.repoze.org/formish"
+ schema="repoze.bfg.formish.zcml.IFormDirective"
+ handler="repoze.bfg.zcml.FormDirective"
+ />
+
+ <meta:directive
+ name="action"
+ namespace="http://namespaces.repoze.org/formish"
+ usedIn="repoze.workflow.zcml.IWorkflowDirective"
+ schema="repoze.workflow.zcml.IActionDirective"
+ handler="repoze.workflow.zcml.ActionDirective"
+ />
+
+ <meta:directive
+ name="add_template_path"
+ namespace="http://namespaces.repoze.org/formish"
+ schema="repoze.bfg.formish.zcml.IAddTemplatePath"
+ handler="repoze.bfg.formish.zcml.add_template_path"
+ />
+
+</configure>
+
148 repoze/bfg/formish/zcml.py
@@ -0,0 +1,148 @@
+import os
+from pkg_resources import resource_filename
+
+from formish import validation
+
+from zope.component import getSiteManager
+import zope.configuration.config
+from zope.configuration.fields import GlobalObject
+
+from zope.interface import Interface
+from zope.interface import implements
+
+from zope.schema import TextLine
+
+from repoze.bfg.zcml import view
+
+from repoze.bfg.formish import Form
+from repoze.bfg.formish import IFormishSearchPath
+
+class IFormDirective(Interface):
+ schema = GlobalObject(title=u'schema', required=True)
+ renderer = TextLine(title=u'renderer', required=True)
+ for_ = GlobalObject(title=u'for', required=False)
+ name = TextLine(title=u'name', required=False)
+ template = TextLine(title=u'template', required=False)
+ permission = TextLine(title=u'permission', required=False)
+ containment = GlobalObject(title=u'containment', required=False)
+ route_name = TextLine(title=u'route_name', required=False)
+ wrapper = TextLine(title = u'wrapper', required=False)
+
+class FormDirective(zope.configuration.config.GroupingContextDecorator):
+ implements(zope.configuration.config.IConfigurationContext,
+ IFormDirective)
+ def __init__(self, context, schema, renderer, for_=None, name='',
+ template=None, permission=None, containment=None,
+ route_name=None, wrapper=None):
+ self.context = context
+ self.schema = schema
+ self.renderer = renderer
+ self.for_ = for_
+ self.name = name
+ self.template = template
+ self.permission = permission
+ self.containment = containment
+ self.route_name = route_name
+ self.wrapper = wrapper
+ self.actions = [] # mutated by subdirectives
+
+ def after(self):
+ def register():
+
+ def form_show_view(context, request):
+ schema = self.schema()
+ form = Form(schema, add_default_action=False)
+ for action in self.actions:
+ form.add_action(action['param'], action['title'])
+ defaults = {'form':form, 'schema':schema}
+ result = self.renderer(context, request, schema, form)
+ if isinstance(result, dict):
+ defaults.update(result)
+ return result
+
+ view(self.context,
+ permission=self.permission,
+ for_=self.for_,
+ view=form_show_view,
+ name=self.name,
+ route_name=self.route_name,
+ containment=self.containment,
+ renderer=self.template,
+ wrapper=self.wrapper)
+
+ for action in self.actions:
+ def form_action_view(context, request):
+ schema = self.schema()
+ form = Form(schema, add_default_action=False)
+ for action in self.actions:
+ form.add_action(action['param'], action['title'])
+ try:
+ converted = form.validate(request,check_form_name=False)
+ return action['success'](context, request, converted)
+ except validation.FormError:
+ defaults = {'form':form, 'schema':schema}
+ result = self.renderer(context, request, schema, form)
+ if isinstance(result, dict):
+ defaults.update(result)
+ return defaults
+ view(self.context,
+ permission=self.permission,
+ for_=self.for_,
+ view=form_action_view,
+ name=self.name,
+ route_name=self.route_name,
+ request_param=action['param'],
+ containment=self.containment,
+ renderer=self.template,
+ wrapper=self.wrapper)
+ self.action(
+ discriminator = None,
+ callable = register,
+ )
+
+class IActionDirective(Interface):
+ """ The interface for an action subdirective """
+ success = GlobalObject(title=u'success handler', required=True)
+ param = TextLine(title=u'param', required=True)
+ title = TextLine(title=u'title', required=False)
+
+def action(context, success, param, title=None):
+ append = context.context.actions.append
+ if title is None:
+ title = param.capitalize()
+ action = {'success':success, 'param':param, 'title':title}
+ append(action)
+
+class IAddTemplatePath(Interface):
+ """
+ Directive for adding a new template path to the chameleon.formish
+ renderer search path.
+ """
+ path = TextLine(
+ title=u"Path spec",
+ description=u'The spec of the template path.',
+ required=True)
+
+def add_template_path(context, path):
+ if os.path.isabs(path):
+ fullpath = path
+ else:
+ if ':' in path:
+ package_name, path = path.split(':', 1)
+ else:
+ package_name = '.'
+ package = context.resolve(package_name)
+ name = package.__name__
+ fullpath = resource_filename(name, path)
+
+ def callback():
+ sm = getSiteManager()
+ search_path = sm.queryUtility(IFormishSearchPath, default=[])
+ search_path.append(fullpath)
+ sm.registerUtility(search_path, IFormishSearchPath)
+
+ context.action(discriminator=None, callable=callback)
+
+
+
+
59 setup.py
@@ -0,0 +1,59 @@
+##############################################################################
+#
+# Copyright (c) 2009 Agendaless Consulting and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the BSD-like license at
+# http://www.repoze.org/LICENSE.txt. A copy of the license 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
+#
+##############################################################################
+
+__version__ = '0.0'
+
+import os
+
+from ez_setup import use_setuptools
+use_setuptools()
+
+from setuptools import setup, find_packages
+
+here = os.path.abspath(os.path.dirname(__file__))
+README = open(os.path.join(here, 'README.txt')).read()
+CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()
+
+requires = [
+ 'setuptools',
+ 'repoze.bfg',
+ 'formish',
+ ]
+
+setup(name='repoze.bfg.formish',
+ version=__version__,
+ description='Formish bindings and helpers for repoze.bfg',
+ long_description=README + '\n\n' + CHANGES,
+ classifiers=[
+ "Intended Audience :: Developers",
+ "Programming Language :: Python",
+ "Topic :: Internet :: WWW/HTTP",
+ "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
+ ],
+ keywords='web formish formgen bfg',
+ author="Agendaless Consulting",
+ author_email="repoze-dev@lists.repoze.org",
+ url="http://www.repoze.org",
+ license="BSD-derived (http://www.repoze.org/LICENSE.txt)",
+ packages=find_packages(),
+ include_package_data=True,
+ namespace_packages=['repoze', 'repoze.bfg'],
+ zip_safe=False,
+ tests_require = requires,
+ install_requires= requires,
+ test_suite="repoze.bfg.formish",
+ entry_points = """\
+ """
+ )
+

0 comments on commit 6c3a2b9

Please sign in to comment.
Something went wrong with that request. Please try again.