Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

preparation for release including checking documentation

  • Loading branch information...
commit a956ae434f000cddc50350b55ef51957b09ed00b 1 parent 830245d
Tim Parkin authored
View
2  docs-build/run
@@ -4,6 +4,6 @@ import os, sys, subprocess
args = sys.argv[1:]
env = dict(os.environ)
-env['PYTHONPATH'] = '.:../../schemaish:../../validatish:../../convertish:../../formish:../../dottedish'
+env['PYTHONPATH'] = '.:../../schemaish:../../validatish:../../convertish:../../formish_new:../../dottedish'
subprocess.call(args, env=env)
View
4 docs/html/.buildinfo
@@ -0,0 +1,4 @@
+# Sphinx build info version 1
+# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
+config: b84f109e8d163d4c04015b12e7cfb349
+tags: fbb0d17656682115ca4d033fb2f83ba1
View
209 docs/html/_sources/introduction.txt
@@ -29,25 +29,14 @@ Data Schema
Any form has to work on a data structure of some sort and it makes sense for this not to be tied in any way to web data. We built a fairly light weight schema library called `schemaish <http://ish.io/projects/show/schemaish>`_ which defines data structures and allows metadata (such as field titles and descriptions) to be assigned. Validation can also be added to each node in a schema (see later). Here are a few examples of creating schema structures.
-.. code-block:: python
-
- import schemaish
-
- schema_item = schemaish.Integer()
-
- # or
+.. doctest::
- my_schema = schemaish.Structure()
- my_schema.add( 'name', schemaish.String() )
- my_schema.add( 'age', schemaish.Integer() )
-
- # or
-
- class MyStructure(schemaish.Structure):
- name = schemaish.String()
- age = schemaish.Integer()
+ >>> import schemaish
+ >>> schema_item = schemaish.Integer()
+ >>> schema_item
+ schemaish.Integer()
-.. doctest:: [basic]
+.. doctest::
>>> import schemaish, validatish
>>> my_schema = schemaish.Structure()
@@ -56,6 +45,16 @@ Any form has to work on a data structure of some sort and it makes sense for thi
>>> my_schema
schemaish.Structure("name": schemaish.String(), "age": schemaish.Integer())
+.. doctest::
+
+ >>> class MyStructure(schemaish.Structure):
+ ... name = schemaish.String()
+ ... age = schemaish.Integer()
+ ...
+ >>> my_schema = MyStructure()
+ >>> my_schema
+ schemaish.Structure("name": schemaish.String(), "age": schemaish.Integer())
+
@@ -71,49 +70,58 @@ In our `validatish <http://ish.io/projects/show/validatish>`_ package, we create
Here is an example of a function based validator
-.. code-block:: python
+.. doctest::
- def is_string(v):
- """ checks that the value is an instance of basestring """
- if v is None:
- return
- msg = "must be a string"
- if not isinstance(v,basestring):
- raise Invalid(msg)
+ >>> import validatish
+ >>> def is_string(v):
+ ... """ checks that the value is an instance of basestring """
+ ... if v is None:
+ ... return
+ ... msg = "must be a string"
+ ... if not isinstance(v,basestring):
+ ... raise validatish.Invalid(msg)
+ ...
+ >>> is_string(1)
+ Traceback (most recent call last):
+ Invalid: must be a string
+ >>> is_string('foo')
+
And here is an example of its matching class based version. We recommend using the class based validators all of the time to keep consistency (you can't use function based validators if the validator needs configuring - schemaish expects a callable that takes a single argument).
-.. code-block:: python
-
- class String(Validator):
- def __call__(self, v):
- validate.is_string(v)
+.. doctest::
+ >>> class String():
+ ... def __call__(self, v):
+ ... is_string(v)
+ ...
+ >>> v = String()
+ >>> v(1)
+ Traceback (most recent call last):
+ Invalid: must be a string
+ >>> v('foo')
-.. note:: If a value is None, then the validation is not applied (this would imply a required constraint also).
+.. note:: If a value is None, then the validation is not applied (as this would imply a required constraint also).
So, now we can pass a validator into one of our schema instances
-.. code-block:: python
+.. doctest::
>>> import schemaish
>>> from validatish import validator
-
- >>> schema = schemaish.String(validator=validator.String()))
-
+ >>> schema = schemaish.String(validator=validator.String())
>>> try:
... schema.validate(10)
... print 'success!'
... except schemaish.Invalid, e:
- ... print 'error',e.error_dict
+ ... print e.error_dict
...
- error {'': 'must be a string'}
-
+ {'': validatish.Invalid("must be a string", validator=validatish.String())}
>>> try:
... schema.validate('foo')
... print 'success!'
... except schemaish.Invalid, e:
- ... print 'error',e.error_dict
+ ... print e.error_dict
...
success!
@@ -121,23 +129,20 @@ So, now we can pass a validator into one of our schema instances
If we apply validators to multiple items in a structure, we can validate them all in one go.
-.. code-block:: python
+.. doctest::
>>> import schemaish
>>> from validatish import validator
-
>>> schema = schemaish.Structure()
>>> schema.add('name', schemaish.String(validator=validator.String()))
>>> schema.add('age', schemaish.Integer(validator=validator.Range(min=18)))
-
>>> try:
... schema.validate({'name': 6, 'age': 17})
... print 'success!'
... except schemaish.Invalid, e:
- ... print 'error',e.error_dict
+ ... print e.error_dict
...
- error {'age': 'must be greater than 18', 'name': 'must be a string'}
-
+ {'age': validatish.Invalid("must be greater than 18", validator=validatish.Range(min=18, max=None)), 'name': validatish.Invalid("must be a string", validator=validatish.String())}
>>> try:
... schema.validate({'name': 'John Drake', 'age': 28})
... print 'success!'
@@ -527,82 +532,44 @@ File Upload
File uploads are notoriously difficult to use in forms. The persistence of uploaded data before the form is finished is messy and a consistent preivew that works for this temporary persistence and also when you've implemented your final store is not straightforward. Formish needs three things for form uploads (and provides defaults for all of them
-FileHandler
-^^^^^^^^^^^
+Filestores
+^^^^^^^^^^
-The filehandlers job is to persist file uploads up until a form is successfully completed. The FileUpload widget asks the filehandler to store the file and remove it after the process has finished. If you want to access the file then it will also give you a direct path for it and a mimtype.
+The filestore job is to persist file uploads up until a form is successfully completed. The FileUpload widget asks the filestore to store the file and remove it after the process has finished. The file is returned as a file handle, mime_type and the filename (or id)
+
+The filestore uses a headered file format which is similar to an email format with one or more lines of key value pairs followed by the binary data. This allows us to store information on the image file such as cache tags and content types. The files are store in a directory given with root_dir and the filestore can also be given a name (which is used in the fileresource to namespace resized image files to prevent clashes between different filestores)
.. code-block:: python
- class TempFileHandler(FileHandlerMinimal):
- """
- File handler using python tempfile module to store file
- """
-
- def __init__(self):
- self.prefix = tempfile.gettempprefix()
- self.tempdir = tempfile.gettempdir()
-
-
- def store_file(self, fieldstorage):
- """
- Given a filehandle, store the file and return an identifier, in this
- case the original filename
- """
- fileno, filename = tempfile.mkstemp( \
- suffix='%s-%s'% (uuid.uuid4().hex,fieldstorage.filename))
- filehandle = os.fdopen(fileno, 'wb')
- filehandle.write(fieldstorage.value)
- filehandle.close()
- filename = ''.join( filename[(len(self.tempdir)+len(self.prefix)+1):] )
- return filename
-
- def delete_file(self, filename):
- """
- remove the tempfile
- """
- filename = '%s/%s%s'% (tempdir, self.prefix, self.filename)
- os.remove(filename)
-
- def get_path_for_file(self, filename):
- """
- given the filename, get the path for the temporary file
- """
- return '%s/%s%s'% (self.tempdir, self.prefix, filename)
-
- def get_mimetype(self, filename):
- """
- use python-magic to guess the mimetype or use application/octet-stream
- if no guess
- """
- mimetype = magic.from_file('%s/%s%s'%(self.tempdir,self.prefix,filename),mime=True)
- return mimetype or 'application/octet-stream'
-
-
-If you want to server the files in your web apllication (and the default FileUpload widget includes facility for an image_preview box) then you'll need to use TempFileHandlerWeb, which includes a resource_root and a fileaccessor
+ class CachedTempFilestore(FileSystemHeaderedFilestore):
-.. code-block:: python
+ def __init__(self, root_dir=None, name=None):
+ if root_dir is None:
+ self._root_dir = tempfile.gettempdir()
+ else:
+ self._root_dir = root_dir
+ if name is None:
+ self.name = ''
+ else:
+ self.name = name
+
+ def get(self, key, cache_tag=None):
+ headers, f = FileSystemHeaderedFilestore.get(self, key)
+ headers = dict(headers)
+ if not cache_tag and headers['Cache-Tag'] == cache_tag:
+ f.close()
+ return (headers['Cache-Tag'], None, None)
+ return (headers['Cache-Tag'],headers['Content-Type'], f)
- class TempFileHandlerWeb(TempFileHandler):
- """
- Same as the temporary file handler but includes ability to include a resource and a url generator (if you want access to the temporary files on the website, e.g. for previews)
- """
- def __init__(self, resource_root='/filehandler',urlfactory=None):
- TempFileHandler.__init__(self)
- self.default_url = default_url
- self.resource_root = resource_root
- self.urlfactory = urlfactory
+ def put(self, key, src, cache_tag, content_type, headers=None):
+ if headers is None:
+ headers = {}
+ headers['Cache-Tag'] = cache_tag
+ headers['Content-Type'] = content_type
+ FileSystemHeaderedFilestore.put(self, key, headers, src)
- def get_url_for_file(self, identifier):
- """
- Generate a url given an identifier
- """
- if self.urlfactory is not None:
- return self.urlfactory(identifier)
- return '%s/%s'% (self.resource_root, identifier)
-This extends the basic tempfile handler to allow you to pass a urlfactory and a root resource for where your assets are mounted.
urlfactory
^^^^^^^^^^
@@ -618,9 +585,9 @@ So here is a simple FileUpload widget
form = formish.Form(schema)
form['myFile'].widget = formish.FileUpload(
- filehandler=formish.TempFileHandlerWeb(),
- originalurl='/images/nouploadyet.png',
- show_image_preview=True
+ formish.filestore.CachedTempFilestore(),
+ image_thumbnail_default='/images/nouploadyet.png',
+ show_image_thumbnail=True
)
@@ -632,12 +599,11 @@ To set up a file resource handler at /filehandler, you could use the following (
@resource.child()
def filehandler(self, request, segments):
- db = collection.CouchishDB(request)
- fa = couchish.FileAccessor(db)
- fh = formish.TempFileHandler()
- return FileResource(fileaccessor=fa,filehandler=fh)
+ tempfilestore = formish.filestore.CachedTempFilestore(name='tmp')
+ filestore = couchish.filestore.CouchDBAttachmentSource(couchish_store,name='cdb')
+ return FileResource(filestores=[filestore, tempfilestore])
-This looks a little more complicated, and is. This resource needs to serve files that are already in your application's persistent storage (the FileAccessor here) and also provide a way of accessing temporary files that have been uploaded as the form is being possibly posted repeatedly before finally succeeding. Don't worry though, if you're happy with using temporary file storage for a while, your resource could look like this
+This looks a little more complicated, and is. This resource needs to serve files that are already in your application's persistent storage (the filestore here) and also provide a way of accessing temporary files that have been uploaded as the form is being possibly posted repeatedly before finally succeeding. Don't worry though, if you're happy with using temporary file storage for a while, your resource could look like this
.. code-block:: python
@@ -649,7 +615,6 @@ Then at some point when you get your storage implemented, you could add your own
We've got a file file examples working at `http://ish.io:8891 <http://ish.io:8891>`_. (Please don't upload any multi megabyte files.. I haven't got a validator on it yet :-)
-I don't think I've explained file uploads as well as I could so perhaps I'll refine this at a later date.
The way forward
---------------
@@ -661,7 +626,7 @@ Slighly bigger pieces of work would be trying to implement some form of wsgi ass
Other goals..
* Multi page forms..
-* relational validation (this required only if that not - is possible now but would like to make more intuitive)
+* relational validation (e.g. this item required only if that not given. this is possible now but would like to make more intuitive)
* GET form submissions
* immutable and plain html versions of templates
View
19 docs/html/_sources/restish.txt
@@ -53,10 +53,8 @@ We'll start off with one of the examples from the Formish documentation.
return self.html(request, form)
return self.thanks(request,data)
- @templating.page('thanks.html')
def thanks(self, request, data):
- """ Just show the data """
- return {'data': data}
+ return http.see_other('/thanks')
We need to have the following in the ``test.html`` file
@@ -78,10 +76,10 @@ We can also pass the success and failure callables to the validate function to s
.. code-block:: python
- class Root(resource.Resource):
+ class Resource(resource.Resource):
@resource.GET()
- @templating.page('test.html')
+ @templating.page('page.html')
def html(self, request, form=None):
if form is None:
form = get_form()
@@ -89,11 +87,10 @@ We can also pass the success and failure callables to the validate function to s
@resource.POST()
def POST(self, request):
- return get_form().validate(request, self.html, self.thanks)
+ return get_contact_form().validate(request, self.html, self.thanks)
- @templating.page('thanks.html')
def thanks(self, request, data):
- return {'data': data}
+ return http.see_other('/thanks')
Multiple Actions on a Form
@@ -186,9 +183,8 @@ If we have more than one form on a page, we can use the utility function, ``form
form['domain'] = self._domain_form()
return {'forms': forms}
- @templating.page('thanks.html')
def thanks(self, request, data):
- pass
+ return http.see_other('/thanks')
We could simplify this further, although I'm not sure this is quite as readable..
@@ -221,6 +217,5 @@ We could simplify this further, although I'm not sure this is quite as readable.
form[f] = self.form(f)
return {'forms': forms}
- @templating.page('thanks.html')
def thanks(self, request, data):
- pass
+ return http.see_other('/thanks')
View
432 docs/html/_sources/walkthrough.txt
@@ -14,55 +14,71 @@ Creating a schema
First of all we need to create a data schema to define what types of data we want in the form. Schema's use the 'Schemaish' package which lets you define structures against which you can validate/convert data. Lets take a look at the structure of a Form instance to begin with
+.. doctest::
->>> import schemaish
->>> schema = schemaish.Structure()
->>> schema.add( 'myfield', schemaish.String() )
->>> schema.attrs
-[('myfield', <schemaish.attr.String object at 0x...>)]
+ >>> import schemaish
+ >>> schema = schemaish.Structure()
+ >>> schema.add( 'myfield', schemaish.String() )
+ >>> schema.attrs
+ [('myfield', schemaish.String())]
Creating a form
---------------
So we now have a single field in our schema which is defined as a string. We can now create a form from this
->>> import formish
->>> form = formish.Form(schema)
+.. doctest::
+
+ >>> import formish
+ >>> form = formish.Form(schema)
Attributes of a form
^^^^^^^^^^^^^^^^^^^^
And what can we do with the form? Well at the moment we have a form name and some fields
->>> form.name
-'formish'
->>> for field in form.fields:
-... print field
-...
-<formish.forms.Field object at 0x...>
+.. doctest::
+
+ >>> form.name
+ 'form'
+
+.. doctest::
+
+ >>> for field in form.fields:
+ ... print field
+ ...
+ formish.Field(name='myfield', attr=schemaish.String())
Attributes of a field
^^^^^^^^^^^^^^^^^^^^^
And what about our field? Well it's now become a form field, which means it has a few extra attributes to do with creating things like classes, ids, etc.
->>> field = form.fields.next()
->>> field.name
-'myfield'
+.. doctest::
+
+ >>> field = form.fields.next()
+ >>> field.name
+ 'myfield'
Obviously the name is what we have it.
->>> field.widget
-<bound widget name="myfield", widget="Input", type="String">
+.. doctest::
->>> field.title
-'Myfield'
+ >>> field.widget
+ BoundWidget(widget=formish.Input(), field=formish.Field(name='myfield', attr=schemaish.String()))
+
+.. doctest::
+
+ >>> field.title
+ 'Myfield'
The title, if not specified in the schema field, is derived from the form name by converting camel case into capitalised words.
->>> field.cssname
-'formish-myfield'
+.. doctest::
+
+ >>> field.cssname
+ 'form-myfield'
This is the start of the templating stuff.. The cssname is an identifier that can be inserted into forms, used for ids and class names.
@@ -71,8 +87,10 @@ How to create HTML
We create our HTML by calling the form as follows..
->>> form()
-'...<form id="form" action="" class="formish-form" method="post" enctype="multipart/form-data" accept-charset="utf-8">...'
+.. doctest::
+
+ >>> form()
+ u'...<form id="form" action="" class="formish-form" method="POST" enctype="multipart/form-data" accept-charset="utf-8">...'
I've skipped the majority of this output as it's probably better shown formatted
@@ -82,7 +100,7 @@ I've skipped the majority of this output as it's probably better shown formatted
<div>
<input type="hidden" name="_charset_" />
<input type="hidden" name="__formish_form__" value="form" />
- <div id="form-myfield-field" class="field string input">
+ <div id="form-myfield--field" class="field type-string widget-input">
<label for="form-myfield">Myfield</label>
<div class="inputs">
<input id="form-myfield" type="text" name="myfield" value="" />
@@ -94,12 +112,18 @@ I've skipped the majority of this output as it's probably better shown formatted
</div>
</form>
+If you want to see the HTML styled with a default CSS stylesheet, take a look at http://test.ish.io or look within the tests directory for a sample restish based website that includes a css file.
+
What's in the HTML
------------------
Firstly we have the form setup itself. The form name/id can be set by passing it into the Form as follows.
->>> named_form = Form(schema,name='myformname')
+.. doctest::
+
+ >>> named_form = formish.Form(schema, name='myformname')
+ >>> named_form
+ formish.Form(schemaish.Structure("myfield": schemaish.String()), name='myformname')
Otherwise the form defaults to 'formish'.
@@ -138,7 +162,7 @@ The input field(s)
.. code-block:: html
<!-- The String Field -->
- <div id="form-myfield-field" class="field string input">
+ <div id="form-myfield--field" class="field type-string widget-input">
<label for="form-myfield">Myfield</label>
<div class="inputs">
<input id="form-myfield" type="text" name="myfield" value="" />
@@ -164,11 +188,14 @@ Processing the Submitted Form
Once the form is submitted, we can get the data by calling 'validate'. In order to simulate this, we're going to create a request object by hand using webob..
->>> import webob
->>> r = webob.Request.blank('http://localhost/', environ={'REQUEST_METHOD': 'POST'})
->>> r.POST['myfield'] = 'myvalue'
->>> form.validate(r)
-{'myfield': u'myvalue'}
+.. doctest::
+
+ >>> import webob
+ >>> r = webob.Request.blank('http://localhost/', environ={'REQUEST_METHOD': 'POST'})
+ >>> r.POST['__formish_form__'] = 'form'
+ >>> r.POST['myfield'] = 'myvalue'
+ >>> form.validate(r)
+ {'myfield': u'myvalue'}
And that is our simple form overview complete.
@@ -187,56 +214,86 @@ Creating the form
For our contrived example, we'll build a simple registration form.
->>> import schemaish
->>> schema = schemaish.Structure()
->>> schema.add( 'firstName', schemaish.String() )
->>> schema.add( 'surname', schemaish.String() )
->>> schema.add( 'dateOfBirth', schemaish.Date() )
->>> schema.add( 'streetNumber', schemaish.Integer() )
->>> schema.add( 'country', schemaish.String() )
->>> schema.add( 'termsAndConditions', schemaish.Boolean() )
->>> form = formish.Form(schema)
+.. doctest::
+
+ >>> import schemaish
+ >>> schema = schemaish.Structure()
+ >>> schema.add( 'firstName', schemaish.String() )
+ >>> schema.add( 'surname', schemaish.String() )
+ >>> schema.add( 'dateOfBirth', schemaish.Date() )
+ >>> schema.add( 'streetNumber', schemaish.Integer() )
+ >>> schema.add( 'country', schemaish.String() )
+ >>> schema.add( 'termsAndConditions', schemaish.Boolean() )
+ >>> form = formish.Form(schema)
+ >>> form
+ formish.Form(schemaish.Structure("firstName": schemaish.String(), "surname": schemaish.String(), "dateOfBirth": schemaish.Date(), "streetNumber": schemaish.Integer(), "country": schemaish.String(), "termsAndConditions": schemaish.Boolean()), name='form')
As you can see, we've got strings, an integer, a data and a boolean.
We could also have built the schema using a declarative style
->>> class MySchema(schemaish.Structure):
-... firstName = schemaish.String()
-... surname = schemaish.String()
-... dateOfBirth = schemaish.Date()
-... streetNumber = schemaish.Integer()
-... country = schemaish.String()
-... termsAndConditions = schemaish.Boolean()
->>> form = formish.Form(MySchema())
+.. doctest::
+
+ >>> class MySchema(schemaish.Structure):
+ ... firstName = schemaish.String()
+ ... surname = schemaish.String()
+ ... dateOfBirth = schemaish.Date()
+ ... streetNumber = schemaish.Integer()
+ ... country = schemaish.String()
+ ... termsAndConditions = schemaish.Boolean()
+ ...
+ >>> form = formish.Form(MySchema())
+ >>> form
+ formish.Form(schemaish.Structure("firstName": schemaish.String(), "surname": schemaish.String(), "dateOfBirth": schemaish.Date(), "streetNumber": schemaish.Integer(), "country": schemaish.String(), "termsAndConditions": schemaish.Boolean()), name='form')
By default, all of the fields use input boxes with the date asking for isoformat and the boolean asking for True or False. We want to make the form a little friendlier though.
We'll start with the date widget. Date parts uses three input boxes instead of a single input and, by default, is in US month first format. We're in the UK so we change dayFirst to True
->>> form['dateOfBirth'].widget = formish.DateParts(dayFirst=True)
+.. doctest::
+
+ >>> form['dateOfBirth'].widget = formish.DateParts(day_first=True)
+ >>> form['dateOfBirth'].widget
+ BoundWidget(widget=formish.DateParts(day_first=True), field=formish.Field(name='dateOfBirth', attr=schemaish.Date()))
+
Next we'll make the country a select box. To do this we pass a series of options to the SelectChoice widget.
->>> form['country'].widget = formish.SelectChoice(options=['UK','US'])
+.. doctest::
+
+ >>> form['country'].widget = formish.SelectChoice(options=['UK','US'])
+ >>> form['country'].widget
+ BoundWidget(widget=formish.SelectChoice(options=[('UK', 'UK'), ('US', 'US')], none_option=(None, '- choose -')), field=formish.Field(name='country', attr=schemaish.String()))
If we wanted different values for our options we would pass each option in as a tuple of ('value','label'). We could also set a label for the field that appears when there is no input value. This is called the 'none_option'. The none_options defaults to ('', '--choose--') so we could change it to ('','Pick a Country'). Here is an example
->>> options = [('UK','I live in the UK'),('US','I live in the US')]
->>> none_option = ('','Where do you live')
->>> form['country'].widget = formish.SelectChoice(options=options, none_option=none_option)
+.. doctest::
+
+ >>> options = [('UK','I live in the UK'),('US','I live in the US')]
+ >>> none_option = ('','Where do you live')
+ >>> form['country'].widget = formish.SelectChoice(options=options, none_option=none_option)
+ >>> form['country'].widget
+ BoundWidget(widget=formish.SelectChoice(options=[('UK', 'I live in the UK'), ('US', 'I live in the US')], none_option=('', 'Where do you live')), field=formish.Field(name='country', attr=schemaish.String()))
Finally, we'd like a checkbox for the Boolean value
->>> form['termsAndConditions'].widget = formish.Checkbox()
+.. doctest::
+
+ >>> form['termsAndConditions'].widget = formish.Checkbox()
+ >>> form['termsAndConditions'].widget
+ BoundWidget(widget=formish.Checkbox(), field=formish.Field(name='termsAndConditions', attr=schemaish.Boolean()))
+
How does this form work?
------------------------
Well let's give it some default values and look at what we get.
->>> import datetime
->>> form.defaults = {'firstName': 'Tim', 'surname': 'Parkin', 'dateOfBirth': datetime.datetime(1966,12,18), 'streetNumber': 123, 'country': 'UK', 'termsAndConditions': False}
+.. doctest::
+
+ >>> import datetime
+ >>> form.defaults = {'firstName': 'Tim', 'surname': 'Parkin', 'dateOfBirth': datetime.datetime(1966,12,18), 'streetNumber': 123, 'country': 'UK', 'termsAndConditions': False}
+
If we create the form now, we get the following fields. One thing to note is that most widgets within Formish use strings to serialise their values into forms. The exception here is the date parts widget.
@@ -247,14 +304,14 @@ This is the same as our first example but note that the label for firstName has
.. code-block:: html
- <div id="form-firstName-field" class="field string input">
+ <div id="form-firstName--field" class="field type-string widget-input">
<label for="form-firstName">First Name</label>
<div class="inputs">
<input id="form-firstName" type="text" name="firstName" value="Tim" />
</div>
</div>
- <div id="form-surname-field" class="field string input">
+ <div id="form-surname--field" class="field type-string widget-input">
<label for="form-surname">Surname</label>
<div class="inputs">
<input id="form-surname" type="text" name="surname" value="Parkin" />
@@ -268,7 +325,7 @@ The date field splits the date into three parts, each part indicated using dotte
.. raw:: html
- <div id="form-dateOfBirth-field" class="field date dateparts">
+ <div id="form-dateOfBirth--field" class="field type-date widget-dateparts">
<label for="form-dateOfBirth">Date Of Birth</label>
<div class="inputs">
<input id="form-dateOfBirth" type="text" name="dateOfBirth.day" value="18" size="2" /> /
@@ -279,7 +336,7 @@ The date field splits the date into three parts, each part indicated using dotte
.. code-block:: html
- <div id="form-dateOfBirth-field" class="field date dateparts">
+ <div id="form-dateOfBirt--field" class="field type-date widget-dateparts">
<label for="form-dateOfBirth">Date Of Birth</label>
<div class="inputs">
<input id="form-dateOfBirth" type="text" name="dateOfBirth.day" value="18" size="2" /> /
@@ -293,7 +350,7 @@ Integer Field
.. code-block:: html
- <div id="form-streetNumber-field" class="field integer input">
+ <div id="form-streetNumber--field" class="field type-integer widget-input">
<label for="form-streetNumber">Street Number</label>
<div class="inputs">
<input id="form-streetNumber" type="text" name="streetNumber" value="123" />
@@ -307,7 +364,7 @@ This uses the 'none_option' value to show 'Where do you live' by default but bec
.. raw:: html
- <div id="form-country-field" class="field string selectchoice">
+ <div id="form-country--field" class="field type-string widget-selectchoice">
<label for="form-country">Country</label>
<div class="inputs">
<select id="form-country" name="country">
@@ -320,7 +377,7 @@ This uses the 'none_option' value to show 'Where do you live' by default but bec
.. code-block:: html
- <div id="form-country-field" class="field string selectchoice">
+ <div id="form-country--field" class="field type-string widget-selectchoice">
<label for="form-country">Country</label>
<div class="inputs">
<select id="form-country" name="country">
@@ -337,7 +394,7 @@ Boolean Field
.. code-block:: html
- <div id="form-termsAndConditions-field" class="field boolean checkbox">
+ <div id="form-termsAndConditions--field" class="field type-boolean widget-checkbox">
<label for="form-termsAndConditions">Terms And Conditions</label>
<div class="inputs">
<input id="form-termsAndConditions" type="checkbox" name="termsAndConditions" value="True" checked="checked" />
@@ -350,31 +407,42 @@ Processing the submitted form
Repeating the creation of a request using webob, setting some input values and validating gives us:
->>> import webob
->>> r = webob.Request.blank('http://localhost/', environ={'REQUEST_METHOD': 'POST'})
->>> r.POST['firstName'] = 'Tim'
->>> r.POST['surname'] = 'Parkin'
->>> r.POST['streetNumber'] = '123'
->>> r.POST['dateOfBirth.day'] = '18'
->>> r.POST['dateOfBirth.month'] = '13'
->>> r.POST['dateOfBirth.year'] = '1966'
->>> r.POST['country'] = 'UK'
->>> r.POST['termsAndConditions'] = 'True'
->>> form.validate(r)
-...formish.validation.FormError: Tried to access data but conversion from request failed with 1 errors
+.. doctest::
+
+ >>> import webob
+ >>> r = webob.Request.blank('http://localhost/', environ={'REQUEST_METHOD': 'POST'})
+ >>> r.POST['__formish_form__'] = 'form'
+ >>> r.POST['firstName'] = 'Tim'
+ >>> r.POST['surname'] = 'Parkin'
+ >>> r.POST['streetNumber'] = '123'
+ >>> r.POST['dateOfBirth.day'] = '18'
+ >>> r.POST['dateOfBirth.month'] = '13'
+ >>> r.POST['dateOfBirth.year'] = '1966'
+ >>> r.POST['country'] = 'UK'
+ >>> r.POST['termsAndConditions'] = 'True'
+ >>> try:
+ ... form.validate(r)
+ ... except formish.FormError, e:
+ ... print e
+ ...
+ Tried to access data but conversion from request failed with 1 errors
The observant amongst you will notice I put a month of 13 in which has triggered a FormError.
Let's look at some of the error states on the form now
->>> form.errors
-{'dateOfBirth': ConvertError('Invalid date: month must be in 1..12',)}
+.. doctest::
+
+ >>> form.errors
+ {'dateOfBirth': 'Invalid date: month must be in 1..12'}
The form has a dictionary of errors on it that map to the field names.
->>> field = form.get_field('dateOfBirth')
->>> field.error
-ConvertError('Invalid date: month must be in 1..12',)
+.. doctest::
+
+ >>> field = form.get_field('dateOfBirth')
+ >>> field.error
+ Invalid date: month must be in 1..12
The dateOfBirth field shows it's own error.
@@ -383,15 +451,17 @@ Showing the errors
The whole form is now in an error state and we can interrogate it about the errors. The form will also render itself with these errors.
->>> field.classes
-'field date dateparts error'
->>> field()
+.. doctest::
+ >>> field.classes
+ 'field date dateparts error'
+ >>> field()
+ '<div id="form-dateOfBirth--field" class="field form-dateOfBirth type-date widget-dateparts error">\n\n<label for="form-dateOfBirth">Date Of Birth</label>\n\n\n<div class="inputs">\n\n\n<input id="form-dateOfBirth" type="text" name="dateOfBirth.day" value="18" size="2" /> /\n<input id="form-dateOfBirth-month" type="text" name="dateOfBirth.month" value="13" size="2" /> /\n<input id="form-dateOfBirth-year" type="text" name="dateOfBirth.year" value="1966" size="4" />\n\n\n</div>\n\n\n<span class="error">Invalid date: month must be in 1..12</span>\n\n\n\n</div>\n'
This produces the following - note the error 'span' below the form field.
.. raw:: html
- <div id="form-dateOfBirth-field" class="field date dateparts error">
+ <div id="form-dateOfBirth--field" class="field type-date widget-dateparts error">
<label for="form-dateOfBirth">Date Of Birth</label>
<div class="inputs">
<input id="form-dateOfBirth" type="text" name="dateOfBirth.day" value="18" size="2" /> /
@@ -403,7 +473,7 @@ This produces the following - note the error 'span' below the form field.
.. code-block:: html
- <div id="form-dateOfBirth-field" class="field date dateparts error">
+ <div id="form-dateOfBirth--field" class="field type-date widget-dateparts error">
<label for="form-dateOfBirth">Date Of Birth</label>
<div class="inputs">
<input id="form-dateOfBirth" type="text" name="dateOfBirth.day" value="18" size="2" /> /
@@ -417,21 +487,28 @@ Calling the 'form()' will render the whole form including the error messages.
Let's see what happens if we have an invalid integer - we'll fix the month first
->>> r.POST['dateOfBirth.month'] = '12'
->>> r.POST['streetNumber'] = 'aa'
->>> form.validate(r)
-...formish.validation.FormError: Tried to access data but conversion from request failed with 1 errors
->>> field = form.get_field('streetNumber')
->>> field.error
-ConvertError('Not a valid number',)
->>>
+.. doctest::
+
+ >>> r.POST['dateOfBirth.month'] = '12'
+ >>> r.POST['streetNumber'] = 'aa'
+ >>> try:
+ ... form.validate(r)
+ ... except formish.FormError, e:
+ ... print e
+ ...
+ Tried to access data but conversion from request failed with 1 errors
+ >>> field = form.get_field('streetNumber')
+ >>> field.error
+ Not a valid integer
Customising Errors
------------------
The errors attribute is available for you to change if you like. You can add your own error to a form by updating or setting the dictionary.
->>> form.errors['country'] = 'You must be outside the UK'
+.. doctest::
+
+ >>> form.errors['country'] = 'You must be outside the UK'
This will automatically add the appropriate error messages in your form just as if you had used a schema validator
@@ -441,9 +518,15 @@ Success!
Finally, lets see what valid data gives us..
->>> r.POST['streetNumber'] = '123'
->>> form.validate(r)
-{'termsAndConditions': True, 'surname': u'Parkin', 'firstName': u'Tim', 'country': u'UK', 'dateOfBirth': datetime.date(1966, 12, 18), 'streetNumber': 123}
+.. doctest::
+
+ >>> r.POST['streetNumber'] = '123'
+ >>> try:
+ ... form.validate(r)
+ ... except formish.FormError, e:
+ ... print e
+ ...
+ {'termsAndConditions': True, 'surname': u'Parkin', 'firstName': u'Tim', 'country': u'UK', 'dateOfBirth': datetime.date(1966, 12, 18), 'streetNumber': 123}
@@ -489,16 +572,26 @@ Here we have an integer validator. This tries to convert the value to an integer
Let's see this one in action
->>> schema = schemaish.Structure()
->>> schema.add( 'myfield', schemaish.Integer(validator=validatish.is_integer) )
->>> form = formish.Form(schema)
->>> r = webob.Request.blank('http://localhost/', environ={'REQUEST_METHOD': 'POST'})
->>> r.POST['myfield'] = 'aa'
->>> form.validate(r)
-...formish.validation.FormError: Tried to access data but conversion from request failed with 1 errors
+.. doctest::
+
+ >>> import validatish
+ >>> schema = schemaish.Structure()
+ >>> schema.add( 'myfield', schemaish.Integer(validator=validatish.is_integer) )
+ >>> form = formish.Form(schema)
+ >>> r = webob.Request.blank('http://localhost/', environ={'REQUEST_METHOD': 'POST'})
+ >>> r.POST['myfield'] = 'aa'
+ >>> r.POST['__formish_form__'] = 'form'
+ >>> try:
+ ... form.validate(r)
+ ... except formish.FormError, e:
+ ... print e
+ ...
+ Tried to access data but conversion from request failed with 1 errors
->>> form.errors
-{'myfield': ConvertError('Not a valid number',)}
+.. doctest::
+
+ >>> form.errors
+ {'myfield': 'Not a valid integer'}
Whilst it is perfectly acceptable to use functions for validation, our main library uses classes to aid type checking (for example to find out if our field is required for css styling) and in order to pass validator configuration.
@@ -536,18 +629,20 @@ Short Version
We handle files for you so that all you have to do is process the file handle given to you.. Here is an example using the default filehandlers..
->>> schema = schemaish.Structure()
->>> schema.add( 'myfile', schemaish.File )
->>> form = formish.Form(schema)
->>> from formish import filehandler
->>> form['myfile'].widget = formish.FileUpload(filehandler=filehandler.TempFileHandlerWeb())
+.. doctest::
+
+ >>> schema = schemaish.Structure()
+ >>> schema.add( 'myfile', schemaish.File )
+ >>> form = formish.Form(schema)
+ >>> from formish import filestore
+ >>> form['myfile'].widget = formish.FileUpload(filestore.CachedTempFilestore())
What does this produce?
^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: html
- <div id="form-myfile-field" class="field type fileupload">
+ <div id="form-myfile--field" class="field type-file widget-fileupload">
<label for="form-myfile">Myfile</label>
<div class="inputs">
<input id="form-myfile-remove" type="checkbox" name="myfile.remove" value="true" />
@@ -561,7 +656,7 @@ and looks like
.. raw:: html
- <div id="form-myfile-field" class="field type fileupload">
+ <div id="form-myfile-field" class="field type-file widget-fileupload">
<label for="form-myfile">Myfile</label>
<div class="inputs">
<input id="form-myfile-remove" type="checkbox" name="myfile.remove" value="true" />
@@ -706,17 +801,22 @@ Checkbox Multi Choice
Now we've talked through the basics.. I'll skip a lot of the detail and just demonstrate the process..
->>> schema = schemaish.Structure()
->>> schema.add( 'myfield', schemaish.Sequence(schemaish.Integer()) )
->>> form = formish.Form(schema)
->>> form.defaults = {'myfield': [2,4]}
->>> form['myfield'].widget = formish.CheckboxMultiChoice(options=[1,2,3,4])
+
+.. doctest::
+
+ >>> schema = schemaish.Structure()
+ >>> schema.add( 'myfield', schemaish.Sequence(schemaish.Integer()) )
+ >>> form = formish.Form(schema)
+ >>> form.defaults = {'myfield': [2,4]}
+ >>> form['myfield'].widget = formish.CheckboxMultiChoice(options=[1,2,3,4])
+ >>> form['myfield'].widget
+ BoundWidget(widget=formish.CheckboxMultiChoice(options=[(1, '1'), (2, '2'), (3, '3'), (4, '4')]), field=formish.Sequence(name='myfield', attr=schemaish.Sequence(schemaish.Integer())))
Let's take a look at the html that produced..
.. raw:: html
- <div id="form-myfield-field" class="field sequence checkboxmultichoice">
+ <div id="form-myfield--field" class="field type-sequence widget-checkboxmultichoice">
<label for="form-myfield">Myfield</label>
<div class="inputs">
<input id="form-myfield-0" name="myfield" type="checkbox" value="1" />
@@ -735,7 +835,7 @@ Let's take a look at the html that produced..
</div>
.. code-block:: html
- <div id="form-myfield-field" class="field sequence checkboxmultichoice">
+ <div id="form-myfield--field" class="field type-sequence widget-checkboxmultichoice">
<label for="form-myfield">Myfield</label>
<div class="inputs">
<input id="form-myfield-0" name="myfield" type="checkbox" value="1" />
@@ -763,15 +863,19 @@ Text Area Sequence
Sometimes it's easier to enter information directly into a textarea
->>> form['myfield'].widget = formish.TextArea()
+.. doctest::
+
+ >>> form['myfield'].widget = formish.TextArea()
Which produces a simple text area as html. When it processes this textarea, it uses the csv module to get the data (it also uses it to put the default data onto the form). By default, the conversion uses commas for a simple sequence. e.g.
->>> form.defaults = {'myfield': [1,3,5,7]}
+.. doctest::
+
+ >>> form.defaults = {'myfield': [1,3,5,7]}
.. raw:: html
- <div id="form-myfield-field" class="field sequence textarea">
+ <div id="form-myfield--field" class="field type-sequence widget-textarea">
<label for="form-myfield">Myfield</label>
<div class="inputs">
<textarea id="form-myfield" name="myfield">1,3,5,7</textarea>
@@ -781,7 +885,7 @@ Which produces a simple text area as html. When it processes this textarea, it u
.. code-block:: html
- <div id="form-myfield-field" class="field sequence textarea">
+ <div id="form-myfield--field" class="field type-sequence widget-textarea">
<label for="form-myfield">Myfield</label>
<div class="inputs">
<textarea id="form-myfield" name="myfield">1,3,5,7</textarea>
@@ -790,8 +894,9 @@ Which produces a simple text area as html. When it processes this textarea, it u
However you can change this behaviour by passing the Textarea widget a converter_option dictionary value .. e.g.
+.. doctest::
->>> form['myfield'].widget = formish.TextArea(converter_options={'delimiter': '\n'})
+ >>> form['myfield'].widget = formish.TextArea(converter_options={'delimiter': '\n'})
.. raw:: html
@@ -809,11 +914,13 @@ Text Area Sequence of Sequences
You can also use a textarea to represent a sequence of sequences...
->>> schema = schemaish.Structure()
->>> schema.add( 'myfield', schemaish.Sequence(schemaish.Sequence(schemaish.Integer())))
->>> form = formish.Form(schema)
->>> form.defaults = {'myfield': [[2,4],[6,8]]}
->>> form['myfield'].widget = formish.TextArea()
+.. doctest::
+
+ >>> schema = schemaish.Structure()
+ >>> schema.add( 'myfield', schemaish.Sequence(schemaish.Sequence(schemaish.Integer())))
+ >>> form = formish.Form(schema)
+ >>> form.defaults = {'myfield': [[2,4],[6,8]]}
+ >>> form['myfield'].widget = formish.TextArea()
In this case, the default delimiter is a comma and is used on a row by row basis.
@@ -841,17 +948,19 @@ Formish also allows you to create sub-sections in forms that can contain any oth
A Structure of Structures
-------------------------
->>> class MyName(schemaish.Structure):
-... firstName = schemaish.String()
-... surname = schemaish.String()
->>> class MyAddress(schemaish.Structure):
-... streetNumber = schemaish.Integer()
-... country = schemaish.String()
->>> class MySchema(schemaish.Structure):
-... name = MyName()
-... address = MyAddress()
-... termsAndConditions = schemaish.Boolean()
->>> form = formish.Form(MySchema())
+.. doctest::
+
+ >>> class MyName(schemaish.Structure):
+ ... firstName = schemaish.String()
+ ... surname = schemaish.String()
+ >>> class MyAddress(schemaish.Structure):
+ ... streetNumber = schemaish.Integer()
+ ... country = schemaish.String()
+ >>> class MySchema(schemaish.Structure):
+ ... name = MyName()
+ ... address = MyAddress()
+ ... termsAndConditions = schemaish.Boolean()
+ >>> form = formish.Form(MySchema())
This will create the following
@@ -861,37 +970,37 @@ This will create the following
<form id="form" action="" method="post" enctype="multipart/form-data" accept-charset="utf-8">
<input type="hidden" name="_charset_" />
<input type="hidden" name="__formish_form__" value="form" />
- <fieldset id="form-name-field" class="field myname sequencedefault sequencecontrols">
+ <fieldset id="form-name--field" class="field myname sequencedefault sequencecontrols">
<legend>Name</legend>
- <div id="form-name-firstName-field" class="field string input">
+ <div id="form-name-firstName--field" class="field type-string widget-input">
<label for="form-name-firstName">First Name</label>
<div class="inputs">
<input id="form-name-firstName" type="text" name="name.firstName" value="" />
</div>
</div>
- <div id="form-name-surname-field" class="field string input">
+ <div id="form-name-surname--field" class="field type-string widget-input">
<label for="form-name-surname">Surname</label>
<div class="inputs">
<input id="form-name-surname" type="text" name="name.surname" value="" />
</div>
</div>
</fieldset>
- <fieldset id="form-address-field" class="field myaddress sequencedefault sequencecontrols">
+ <fieldset id="form-address--field" class="field myaddress widget-sequencedefault sequencecontrols">
<legend>Address</legend>
- <div id="form-address-streetNumber-field" class="field integer input">
+ <div id="form-address-streetNumber--field" class="field type-integer widget-input">
<label for="form-address-streetNumber">Street Number</label>
<div class="inputs">
<input id="form-address-streetNumber" type="text" name="address.streetNumber" value="" />
</div>
</div>
- <div id="form-address-country-field" class="field string input">
+ <div id="form-address-country--field" class="field type-string widget-input">
<label for="form-address-country">Country</label>
<div class="inputs">
<input id="form-address-country" type="text" name="address.country" value="" />
</div>
</div>
</fieldset>
- <div id="form-termsAndConditions-field" class="field boolean input">
+ <div id="form-termsAndConditions--field" class="field type-boolean widget-input">
<label for="form-termsAndConditions">Terms And Conditions</label>
<div class="inputs">
<input id="form-termsAndConditions" type="text" name="termsAndConditions" value="" />
@@ -907,37 +1016,37 @@ This will create the following
<form id="form" action="" method="post" enctype="multipart/form-data" accept-charset="utf-8">
<input type="hidden" name="_charset_" />
<input type="hidden" name="__formish_form__" value="form" />
- <fieldset id="form-name-field" class="field myname sequencedefault sequencecontrols">
+ <fieldset id="form-name--field" class="field myname sequencedefault sequencecontrols">
<legend>Name</legend>
- <div id="form-name-firstName-field" class="field string input">
+ <div id="form-name-firstName--field" class="field type-string widget-input">
<label for="form-name-firstName">First Name</label>
<div class="inputs">
<input id="form-name-firstName" type="text" name="name.firstName" value="" />
</div>
</div>
- <div id="form-name-surname-field" class="field string input">
+ <div id="form-name-surname--field" class="field type-string widget-input">
<label for="form-name-surname">Surname</label>
<div class="inputs">
<input id="form-name-surname" type="text" name="name.surname" value="" />
</div>
</div>
</fieldset>
- <fieldset id="form-address-field" class="field myaddress sequencedefault sequencecontrols">
+ <fieldset id="form-address--field" class="field myaddress widget-sequencedefault sequencecontrols">
<legend>Address</legend>
- <div id="form-address-streetNumber-field" class="field integer input">
+ <div id="form-address-streetNumber--field" class="field type-integer widget-input">
<label for="form-address-streetNumber">Street Number</label>
<div class="inputs">
<input id="form-address-streetNumber" type="text" name="address.streetNumber" value="" />
</div>
</div>
- <div id="form-address-country-field" class="field string input">
+ <div id="form-address-country--field" class="field type-string widget-input">
<label for="form-address-country">Country</label>
<div class="inputs">
<input id="form-address-country" type="text" name="address.country" value="" />
</div>
</div>
</fieldset>
- <div id="form-termsAndConditions-field" class="field boolean input">
+ <div id="form-termsAndConditions--field" class="field type-boolean widget-input">
<label for="form-termsAndConditions">Terms And Conditions</label>
<div class="inputs">
<input id="form-termsAndConditions" type="text" name="termsAndConditions" value="" />
@@ -967,9 +1076,12 @@ A tuple can be used to create a list that has different types in it. For instanc
Lets see how that works.
->>> schema.add( 'myfield', schemaish.Sequence( schemaish.Tuple( schemaish.Integer(), schemaish.Date() ) ) )
->>> form = formish.Form(schema)
->>> form['myfield'].widget=formish.TextArea()
+.. doctest::
+
+ >>> schema = schemaish.Structure()
+ >>> schema.add( 'myfield', schemaish.Sequence( schemaish.Tuple( (schemaish.Integer(), schemaish.Date()) ) ) )
+ >>> form = formish.Form(schema)
+ >>> form['myfield'].widget=formish.TextArea()
You will now get a textarea that will return validation messages if it is unable to convert to integers or strings and that will output appropriately typed data.
View
414 docs/html/_static/basic.css
@@ -0,0 +1,414 @@
+/**
+ * Sphinx stylesheet -- basic theme
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+/* -- main layout ----------------------------------------------------------- */
+
+div.documentwrapper {
+ float: left;
+ width: 100%;
+}
+
+div.bodywrapper {
+ margin: 0 0 0 230px;
+}
+
+div.clearer {
+ clear: both;
+}
+
+/* -- relbar ---------------------------------------------------------------- */
+
+div.related {
+ width: 100%;
+ font-size: 90%;
+}
+
+div.related h3 {
+ display: none;
+}
+
+div.related ul {
+ margin: 0;
+ padding: 0 0 0 10px;
+ list-style: none;
+}
+
+div.related li {
+ display: inline;
+}
+
+div.related li.right {
+ float: right;
+ margin-right: 5px;
+}
+
+/* -- sidebar --------------------------------------------------------------- */
+
+div.sphinxsidebarwrapper {
+ padding: 10px 5px 0 10px;
+}
+
+div.sphinxsidebar {
+ float: left;
+ width: 230px;
+ margin-left: -100%;
+ font-size: 90%;
+}
+
+div.sphinxsidebar ul {
+ list-style: none;
+}
+
+div.sphinxsidebar ul ul,
+div.sphinxsidebar ul.want-points {
+ margin-left: 20px;
+ list-style: square;
+}
+
+div.sphinxsidebar ul ul {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+div.sphinxsidebar form {
+ margin-top: 10px;
+}
+
+div.sphinxsidebar input {
+ border: 1px solid #98dbcc;
+ font-family: sans-serif;
+ font-size: 1em;
+}
+
+img {
+ border: 0;
+}
+
+/* -- search page ----------------------------------------------------------- */
+
+ul.search {
+ margin: 10px 0 0 20px;
+ padding: 0;
+}
+
+ul.search li {
+ padding: 5px 0 5px 20px;
+ background-image: url(file.png);
+ background-repeat: no-repeat;
+ background-position: 0 7px;
+}
+
+ul.search li a {
+ font-weight: bold;
+}
+
+ul.search li div.context {
+ color: #888;
+ margin: 2px 0 0 30px;
+ text-align: left;
+}
+
+ul.keywordmatches li.goodmatch a {
+ font-weight: bold;
+}
+
+/* -- index page ------------------------------------------------------------ */
+
+table.contentstable {
+ width: 90%;
+}
+
+table.contentstable p.biglink {
+ line-height: 150%;
+}
+
+a.biglink {
+ font-size: 1.3em;
+}
+
+span.linkdescr {
+ font-style: italic;
+ padding-top: 5px;
+ font-size: 90%;
+}
+
+/* -- general index --------------------------------------------------------- */
+
+table.indextable td {
+ text-align: left;
+ vertical-align: top;
+}
+
+table.indextable dl, table.indextable dd {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+table.indextable tr.pcap {
+ height: 10px;
+}
+
+table.indextable tr.cap {
+ margin-top: 10px;
+ background-color: #f2f2f2;
+}
+
+img.toggler {
+ margin-right: 3px;
+ margin-top: 3px;
+ cursor: pointer;
+}
+
+/* -- general body styles --------------------------------------------------- */
+
+a.headerlink {
+ visibility: hidden;
+}
+
+h1:hover > a.headerlink,
+h2:hover > a.headerlink,
+h3:hover > a.headerlink,
+h4:hover > a.headerlink,
+h5:hover > a.headerlink,
+h6:hover > a.headerlink,
+dt:hover > a.headerlink {
+ visibility: visible;
+}
+
+div.body p.caption {
+ text-align: inherit;
+}
+
+div.body td {
+ text-align: left;
+}
+
+.field-list ul {
+ padding-left: 1em;
+}
+
+.first {
+ margin-top: 0 !important;
+}
+
+p.rubric {
+ margin-top: 30px;
+ font-weight: bold;
+}
+
+/* -- sidebars -------------------------------------------------------------- */
+
+div.sidebar {
+ margin: 0 0 0.5em 1em;
+ border: 1px solid #ddb;
+ padding: 7px 7px 0 7px;
+ background-color: #ffe;
+ width: 40%;
+ float: right;
+}
+
+p.sidebar-title {
+ font-weight: bold;
+}
+
+/* -- topics ---------------------------------------------------------------- */
+
+div.topic {
+ border: 1px solid #ccc;
+ padding: 7px 7px 0 7px;
+ margin: 10px 0 10px 0;
+}
+
+p.topic-title {
+ font-size: 1.1em;
+ font-weight: bold;
+ margin-top: 10px;
+}
+
+/* -- admonitions ----------------------------------------------------------- */
+
+div.admonition {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ padding: 7px;
+}
+
+div.admonition dt {
+ font-weight: bold;
+}
+
+div.admonition dl {
+ margin-bottom: 0;
+}
+
+p.admonition-title {
+ margin: 0px 10px 5px 0px;
+ font-weight: bold;
+}
+
+div.body p.centered {
+ text-align: center;
+ margin-top: 25px;
+}
+
+/* -- tables ---------------------------------------------------------------- */
+
+table.docutils {
+ border: 0;
+ border-collapse: collapse;
+}
+
+table.docutils td, table.docutils th {
+ padding: 1px 8px 1px 0;
+ border-top: 0;
+ border-left: 0;
+ border-right: 0;
+ border-bottom: 1px solid #aaa;
+}
+
+table.field-list td, table.field-list th {
+ border: 0 !important;
+}
+
+table.footnote td, table.footnote th {
+ border: 0 !important;
+}
+
+th {
+ text-align: left;
+ padding-right: 5px;
+}
+
+/* -- other body styles ----------------------------------------------------- */
+
+dl {
+ margin-bottom: 15px;
+}
+
+dd p {
+ margin-top: 0px;
+}
+
+dd ul, dd table {
+ margin-bottom: 10px;
+}
+
+dd {
+ margin-top: 3px;
+ margin-bottom: 10px;
+ margin-left: 30px;
+}
+
+dt:target, .highlight {
+ background-color: #fbe54e;
+}
+
+dl.glossary dt {
+ font-weight: bold;
+ font-size: 1.1em;
+}
+
+.field-list ul {
+ margin: 0;
+ padding-left: 1em;
+}
+
+.field-list p {
+ margin: 0;
+}
+
+.refcount {
+ color: #060;
+}
+
+.optional {
+ font-size: 1.3em;
+}
+
+.versionmodified {
+ font-style: italic;
+}
+
+.system-message {
+ background-color: #fda;
+ padding: 5px;
+ border: 3px solid red;
+}
+
+.footnote:target {
+ background-color: #ffa
+}
+
+/* -- code displays --------------------------------------------------------- */
+
+pre {
+ overflow: auto;
+}
+
+td.linenos pre {
+ padding: 5px 0px;
+ border: 0;
+ background-color: transparent;
+ color: #aaa;
+}
+
+table.highlighttable {
+ margin-left: 0.5em;
+}
+
+table.highlighttable td {
+ padding: 0 0.5em 0 0.5em;
+}
+
+tt.descname {
+ background-color: transparent;
+ font-weight: bold;
+ font-size: 1.2em;
+}
+
+tt.descclassname {
+ background-color: transparent;
+}
+
+tt.xref, a tt {
+ background-color: transparent;
+ font-weight: bold;
+}
+
+h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt {
+ background-color: transparent;
+}
+
+/* -- math display ---------------------------------------------------------- */
+
+img.math {
+ vertical-align: middle;
+}
+
+div.math p {
+ text-align: center;
+}
+
+span.eqno {
+ float: right;
+}
+
+/* -- printout stylesheet --------------------------------------------------- */
+
+@media print {
+ div.document,
+ div.documentwrapper,
+ div.bodywrapper {
+ margin: 0;
+ width: 100%;
+ }
+
+ div.sphinxsidebar,
+ div.related,
+ div.footer,
+ #top-link {
+ display: none;
+ }
+}
View
528 docs/html/_static/default.css
@@ -1,7 +1,12 @@
/**
- * Sphinx Doc Design
+ * Sphinx stylesheet -- default theme
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
+@import url("basic.css");
+
+/* -- page layout ----------------------------------------------------------- */
+
body {
font-family: sans-serif;
font-size: 100%;
@@ -11,43 +16,18 @@ body {
padding: 0;
}
-/* :::: LAYOUT :::: */
-
div.document {
background-color: #1c4e63;
}
-div.documentwrapper {
- float: left;
- width: 100%;
-}
-
-div.bodywrapper {
- margin: 0 0 0 230px;
-}
-
div.body {
- background-color: white;
+ background-color: #ffffff;
+ color: #000000;
padding: 0 20px 30px 20px;
}
-div.sphinxsidebarwrapper {
- padding: 10px 5px 0 10px;
-}
-
-div.sphinxsidebar {
- float: left;
- width: 230px;
- margin-left: -100%;
- font-size: 90%;
-}
-
-div.clearer {
- clear: both;
-}
-
div.footer {
- color: #fff;
+ color: #ffffff;
width: 100%;
padding: 9px 0 9px 0;
text-align: center;
@@ -55,45 +35,26 @@ div.footer {
}
div.footer a {
- color: #fff;
+ color: #ffffff;
text-decoration: underline;
}
div.related {
background-color: #133f52;
- color: #fff;
- width: 100%;
line-height: 30px;
- font-size: 90%;
-}
-
-div.related h3 {
- display: none;
-}
-
-div.related ul {
- margin: 0;
- padding: 0 0 0 10px;
- list-style: none;
-}
-
-div.related li {
- display: inline;
+ color: #ffffff;
}
-div.related li.right {
- float: right;
- margin-right: 5px;
+div.related a {
+ color: #ffffff;
}
-div.related a {
- color: white;
+div.sphinxsidebar {
}
-/* ::: TOC :::: */
div.sphinxsidebar h3 {
font-family: 'Trebuchet MS', sans-serif;
- color: white;
+ color: #ffffff;
font-size: 1.4em;
font-weight: normal;
margin: 0;
@@ -101,12 +62,12 @@ div.sphinxsidebar h3 {
}
div.sphinxsidebar h3 a {
- color: white;
+ color: #ffffff;
}
div.sphinxsidebar h4 {
font-family: 'Trebuchet MS', sans-serif;
- color: white;
+ color: #ffffff;
font-size: 1.3em;
font-weight: normal;
margin: 5px 0 0 0;
@@ -114,7 +75,7 @@ div.sphinxsidebar h4 {
}
div.sphinxsidebar p {
- color: white;
+ color: #ffffff;
}
div.sphinxsidebar p.topless {
@@ -124,171 +85,20 @@ div.sphinxsidebar p.topless {
div.sphinxsidebar ul {
margin: 10px;
padding: 0;
- list-style: none;
- color: white;
-}
-
-div.sphinxsidebar ul ul,
-div.sphinxsidebar ul.want-points {
- margin-left: 20px;
- list-style: square;
-}
-
-div.sphinxsidebar ul ul {
- margin-top: 0;
- margin-bottom: 0;
+ color: #ffffff;
}
div.sphinxsidebar a {
color: #98dbcc;
}
-div.sphinxsidebar form {
- margin-top: 10px;
-}
-
div.sphinxsidebar input {
border: 1px solid #98dbcc;
font-family: sans-serif;
font-size: 1em;
}
-/* :::: MODULE CLOUD :::: */
-div.modulecloud {
- margin: -5px 10px 5px 10px;
- padding: 10px;
- line-height: 160%;
- border: 1px solid #cbe7e5;
- background-color: #f2fbfd;
-}
-
-div.modulecloud a {
- padding: 0 5px 0 5px;
-}
-
-/* :::: SEARCH :::: */
-ul.search {
- margin: 10px 0 0 20px;
- padding: 0;
-}
-
-ul.search li {
- padding: 5px 0 5px 20px;
- background-image: url(file.png);
- background-repeat: no-repeat;
- background-position: 0 7px;
-}
-
-ul.search li a {
- font-weight: bold;
-}
-
-ul.search li div.context {
- color: #888;
- margin: 2px 0 0 30px;
- text-align: left;
-}
-
-ul.keywordmatches li.goodmatch a {
- font-weight: bold;
-}
-
-/* :::: COMMON FORM STYLES :::: */
-
-div.actions {
- padding: 5px 10px 5px 10px;
- border-top: 1px solid #cbe7e5;
- border-bottom: 1px solid #cbe7e5;
- background-color: #e0f6f4;
-}
-
-form dl {
- color: #333;
-}
-
-form dt {
- clear: both;
- float: left;
- min-width: 110px;
- margin-right: 10px;
- padding-top: 2px;
-}
-
-input#homepage {
- display: none;
-}
-
-div.error {
- margin: 5px 20px 0 0;
- padding: 5px;
- border: 1px solid #d00;
- font-weight: bold;
-}
-
-/* :::: INDEX PAGE :::: */
-
-table.contentstable {
- width: 90%;
-}
-
-table.contentstable p.biglink {
- line-height: 150%;
-}
-
-a.biglink {
- font-size: 1.3em;
-}
-
-span.linkdescr {
- font-style: italic;
- padding-top: 5px;
- font-size: 90%;
-}
-
-/* :::: INDEX STYLES :::: */
-
-table.indextable td {
- text-align: left;
- vertical-align: top;
-}
-
-table.indextable dl, table.indextable dd {
- margin-top: 0;
- margin-bottom: 0;
-}
-
-table.indextable tr.pcap {
- height: 10px;
-}
-
-table.indextable tr.cap {
- margin-top: 10px;
- background-color: #f2f2f2;
-}
-
-img.toggler {
- margin-right: 3px;
- margin-top: 3px;
- cursor: pointer;
-}
-
-form.pfform {
- margin: 10px 0 20px 0;
-}
-
-/* :::: GLOBAL STYLES :::: */
-
-.docwarning {
- background-color: #ffe4e4;
- padding: 10px;
- margin: 0 -20px 0 -20px;
- border-bottom: 1px solid #f66;
-}
-
-p.subhead {
- font-weight: bold;
- margin-top: 20px;
-}
+/* -- body styles ----------------------------------------------------------- */
a {
color: #355f7c;
@@ -299,6 +109,11 @@ a:hover {
text-decoration: underline;
}
+div.body p, div.body dd, div.body li {
+ text-align: justify;
+ line-height: 130%;
+}
+
div.body h1,
div.body h2,
div.body h3,
@@ -326,17 +141,6 @@ a.headerlink {
font-size: 0.8em;
padding: 0 4px 0 4px;
text-decoration: none;
- visibility: hidden;
-}
-
-h1:hover > a.headerlink,
-h2:hover > a.headerlink,
-h3:hover > a.headerlink,
-h4:hover > a.headerlink,
-h5:hover > a.headerlink,
-h6:hover > a.headerlink,
-dt:hover > a.headerlink {
- visibility: visible;
}
a.headerlink:hover {
@@ -349,82 +153,13 @@ div.body p, div.body dd, div.body li {
line-height: 130%;
}
-div.body p.caption {
- text-align: inherit;
-}
-
-div.body td {
- text-align: left;
-}
-
-ul.fakelist {
- list-style: none;
- margin: 10px 0 10px 20px;
- padding: 0;
-}
-
-.field-list ul {
- padding-left: 1em;
-}
-
-.first {
- margin-top: 0 !important;
-}
-
-/* "Footnotes" heading */
-p.rubric {
- margin-top: 30px;
- font-weight: bold;
-}
-
-/* Sidebars */
-
-div.sidebar {
- margin: 0 0 0.5em 1em;
- border: 1px solid #ddb;
- padding: 7px 7px 0 7px;
- background-color: #ffe;
- width: 40%;
- float: right;
-}
-
-p.sidebar-title {
- font-weight: bold;
+div.admonition p.admonition-title + p {
+ display: inline;
}
-/* "Topics" */
-
-div.topic {
+div.note {
background-color: #eee;
border: 1px solid #ccc;
- padding: 7px 7px 0 7px;
- margin: 10px 0 10px 0;
-}
-
-p.topic-title {
- font-size: 1.1em;
- font-weight: bold;
- margin-top: 10px;
-}
-
-/* Admonitions */
-
-div.admonition {
- margin-top: 10px;
- margin-bottom: 10px;
- padding: 7px;
-}
-
-div.admonition dt {
- font-weight: bold;
-}
-
-div.admonition dl {
- margin-bottom: 0;
-}
-
-div.admonition p.admonition-title + p {
- display: inline;
}
div.seealso {
@@ -432,19 +167,16 @@ div.seealso {
border: 1px solid #ff6;
}
+div.topic {
+ background-color: #eee;
+}
+
div.warning {
background-color: #ffe4e4;
border: 1px solid #f66;
}
-div.note {
- background-color: #eee;
- border: 1px solid #ccc;
-}
-
p.admonition-title {
- margin: 0px 10px 5px 0px;
- font-weight: bold;
display: inline;
}
@@ -452,206 +184,18 @@ p.admonition-title:after {
content: ":";
}
-div.body p.centered {
- text-align: center;
- margin-top: 25px;
-}
-
-table.docutils {
- border: 0;
-}
-
-table.docutils td, table.docutils th {
- padding: 1px 8px 1px 0;
- border-top: 0;
- border-left: 0;
- border-right: 0;
- border-bottom: 1px solid #aaa;
-}
-
-table.field-list td, table.field-list th {
- border: 0 !important;
-}
-
-table.footnote td, table.footnote th {
- border: 0 !important;
-}
-
-.field-list ul {
- margin: 0;
- padding-left: 1em;
-}
-
-.field-list p {
- margin: 0;
-}
-
-dl {
- margin-bottom: 15px;
- clear: both;
-}
-
-dd p {
- margin-top: 0px;
-}
-
-dd ul, dd table {
- margin-bottom: 10px;
-}
-
-dd {
- margin-top: 3px;
- margin-bottom: 10px;
- margin-left: 30px;
-}
-
-.refcount {
- color: #060;
-}
-
-dt:target,
-.highlight {
- background-color: #fbe54e;
-}
-
-dl.glossary dt {
- font-weight: bold;
- font-size: 1.1em;
-}
-
-th {
- text-align: left;
- padding-right: 5px;
-}
-
pre {
padding: 5px;
- background-color: #efc;
- color: #333;
+ background-color: #eeffcc;
+ color: #333333;
+ line-height: 120%;
border: 1px solid #ac9;
border-left: none;
border-right: none;
- overflow: auto;
-}
-
-td.linenos pre {
- padding: 5px 0px;
- border: 0;
- background-color: transparent;
- color: #aaa;
-}
-
-table.highlighttable {
- margin-left: 0.5em;
-}
-
-table.highlighttable td {
- padding: 0 0.5em 0 0.5em;
}
tt {
background-color: #ecf0f3;
padding: 0 1px 0 1px;
font-size: 0.95em;
-}
-
-tt.descname {
- background-color: transparent;
- font-weight: bold;
- font-size: 1.2em;
-}
-
-tt.descclassname {
- background-color: transparent;
-}
-
-tt.xref, a tt {
- background-color: transparent;
- font-weight: bold;
-}
-
-.footnote:target { background-color: #ffa }
-
-h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt {
- background-color: transparent;
-}
-
-.optional {
- font-size: 1.3em;
-}
-
-.versionmodified {
- font-style: italic;
-}
-
-form.comment {
- margin: 0;
- padding: 10px 30px 10px 30px;
- background-color: #eee;
-}
-
-form.comment h3 {
- background-color: #326591;
- color: white;
- margin: -10px -30px 10px -30px;
- padding: 5px;
- font-size: 1.4em;
-}
-
-form.comment input,
-form.comment textarea {
- border: 1px solid #ccc;
- padding: 2px;
- font-family: sans-serif;
- font-size: 100%;
-}
-
-form.comment input[type="text"] {
- width: 240px;
-}
-
-form.comment textarea {
- width: 100%;
- height: 200px;
- margin-bottom: 10px;
-}
-
-.system-message {
- background-color: #fda;
- padding: 5px;
- border: 3px solid red;
-}
-
-img.math {
- vertical-align: middle;
-}
-
-div.math p {
- text-align: center;
-}
-
-span.eqno {
- float: right;
-}
-
-img.logo {
- border: 0;
-}
-
-/* :::: PRINT :::: */
-@media print {
- div.document,
- div.documentwrapper,
- div.bodywrapper {
- margin: 0;
- width : 100%;
- }
-
- div.sphinxsidebar,
- div.related,
- div.footer,
- div#comments div.new-comment-box,
- #top-link {
- display: none;
- }
-}
+}
View
4 docs/html/_static/doctools.js
@@ -133,13 +133,13 @@ var Documentation = {
* add context elements like header anchor links
*/
addContextElements : function() {
- $('div[@id] > :header:first').each(function() {
+ $('div[id] > :header:first').each(function() {
$('<a class="headerlink">\u00B6</a>').
attr('href', '#' + this.id).
attr('title', _('Permalink to this headline')).
appendTo(this);
});
- $('dt[@id]').each(function() {
+ $('dt[id]').each(function() {
$('<a class="headerlink">\u00B6</a>').
attr('href', '#' + this.id).
attr('title', _('Permalink to this definition')).
View
4 docs/html/_static/searchtools.js
@@ -221,7 +221,7 @@ var Search = {
var params = $.getQueryParameters();
if (params.q) {
var query = params.q[0];
- $('input[@name="q"]')[0].value = query;
+ $('input[name="q"]')[0].value = query;
this.performSearch(query);
}
},
@@ -283,7 +283,7 @@ var Search = {
if (this.hasIndex())
this.query(query);
else
- this.setQuery(query);
+ this.deferQuery(query);
},
query : function(query) {
View
50 docs/html/formish.filehandler.html
@@ -3,7 +3,7 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- <title>formish.filehandler &mdash; Formish v1 documentation</title>
+ <title>formish.filehandler &amp;mdash; Formish v1 documentation</title>
<link rel="stylesheet" href="_static/default.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<script type=