pyramid-formencode-classic


Namepyramid-formencode-classic JSON
Version 0.6.0 PyPI version JSON
download
home_pagehttps://github.com/jvanasco/pyramid_formencode_classic
Summary('An implementation of the classic Pylons formencode validation, for Pyramid',)
upload_time2024-04-09 20:25:02
maintainerNone
docs_urlNone
authorJonathan Vanasco
requires_pythonNone
licenseBSD
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            ![Python package](https://github.com/jvanasco/pyramid_formencode_classic/workflows/Python%20package/badge.svg)

## Current Recommended Version

`v 0.6.0 (2024.04.09)`

This is backwards compatible due to the following change:

    * `form_validate` now requires `return_stash=True` - which is enabled by default.
      To support `return_stash=False`, please call `form_validate_simple` instead.
      This change was required to streamline typing and better standardize the library.
      Background:
        `form_validate` was renamed to `_form_validate_core`
        the new `form_validate` ensures `return_stash==True` and invokes `_form_validate_core`
        the new `form_validate_simple` ensures `return_stash==False` and invokes `_form_validate_core`

## Current EOL version

`v 0.5.0 (2023.06.xx)`

New Features:
* mypy support
* drop python2
* project reorganized


## Last Python2 Version

`v 0.4.4 (2020.10.20)`

New Features:

* simplified api
* bug fixes, better integration
* python3 support
* pyramid debugtoolbar support
* automatic handling of edge cases with ForEach validator errors


### Backwards Compatible?

### 0.6.0

A backwards incompatible change was introduced.

form_validate` now requires `return_stash=True` - which is enabled by default.
To support `return_stash=False`, please call `form_validate_simple` instead.

### 0.5.0

The library was reorganized to implement typing.
Most uses should be backwards compatible.
It is possible an object you used moved. If so, please file a ticket to see if we can pull it back in.
The default values were moved to a separate file. Those should never have been referenced in code.


### 0.4.0

* `0.3.x` No

### 0.3.0

* `0.2.x` Yes.
* `0.1.x` No. Some functionality switched between `0.1.10` and `0.2.0` and light editing is required.  See `CHANGES.txt` for full details.  Also see the migration guide below.


## Installation

This requires the 2.0 branch of formencode, which has still been an alpha release since Aug 9, 2016

	pip install formencode==2.0.0a1
	pip install pyramid_formencode_classic


## What is this package?

`pyramid_formencode_classic` is a port of some classic `Pylons` form validation concepts onto the `Pyramid` framework.

Through the version `0.1.x` releases, this package sought to integrate the Pylons validation onto Pyramid so projects could be ported easier.

Starting with version `0.2.0` strict backwards compatibility has been lost in favor of performance enhancements and streamlining the API. There were simply a handful of bugs and oddities which were not easily fixed.

## How does this handle form validation?

In the example below, form validation is broken into 4 components:

* A `formencode` form schema
* a callable dispatcher (`login`)
* a private printer (`_login_print`)
* a private submit processor (`_login_submit`)

The formencode schema does not interact with the database. It is used only for "lightweight" validation and cheap operations (length, presence, etc).

The more involved operations, such as checking a database within a transaction, can occur in the submit step.  

In this pattern, if an error is encountered at any time, a `FormInvalid` error can be raised to trigger `form_reprint`.  That function will render the template using `Pyramid`'s mechanism and then run `formencode`'s `htmlfill` on it.

If you want to set a global "oh noes!" message for the form, set an error on a special non-existent field like `Error_Main`.


## Pyramid Integration

Just do this...

	config.include('pyramid_formencode_classic')
	
Which will invoke `Pyramid`'s `add_request_method` to add a new attribute to your request.

`request.pyramid_formencode_classic` will be a per-request instance of `pyramid_formencode_classic.FormStashList`.

Parsing a form will manage the formdata in `request.pyramid_formencode_classic['_default']` the default form stash.

If you want to specify a particular stash, because you use multiple forms on a page or have other needs:

* `request.pyramid_formencode_classic.get_form(...)` accepts a `form_stash` kwarg, which defaults to `_default`
* `form_validate(...)` accepts a `form_stash` kwarg, which defaults to `_default`
* `form_reprint(...)` accepts a `form_stash` kwarg, which defaults to `_default`


## Caveats, Oddities, Etc

### Custom Errors, Custom Error Displays and Missing Fields

#### Where are errors placed?  What about missing fields?

`formencode.htmlfill` prefers to upgrade a html form element with the error information.

If the html input for an error is missing, such as a custom `Error_Main` field, `formencode` will attempt to do two things:

1. `formencode` will look for a custom `form:error` field, such as `<form:error name="Error_Main"/>`.
2. If no fields are available, `formencode` will *PREPEND* the error messages to the document.  This can create problems if you are running the reprint on a full (not partial) html page.

#### How are errors styled?

`formencode` styles errors using two commandline arguments.

* `auto_error_formatter` is a function that formats the error messages for fields which do not appear on the document and are pre-pended.
* `error_formatters` is a dict of error formatters that can be passed into `htmlfill`.  if provided, these will be merged into the htmlfill defaults.

`htmlfill` allows a bit of customization by supporting a `format` attribute in `<form:error/>` declarations, which will invoke the respective entry in the `error_formatters` dict.

#### How can a "global" form error be handled?

Handling a custom error can be achieved by reserving a special `error_main` key. By default, `pyramid_formencode_classic` uses `Error_Main`.

Once you set that field as a form error,  `formencode.htmlfill` will replace this markup in your template

    <form:error name="Error_Main"/>

with the following html:

    <!-- for: Error_Main -->
	<span class="error-message">%(Error_Main)s</span><br/>

In which the `Error_main` text has been run through `error_formatters['default']`

There is a small caveat:

In order for the text to appear in the form where you wish, you must write `<form:error name="Error_Main"/>` in the form.  Non-error views will contain that text in the html source, but not render it; error views will replace it with properly formatted errors.

This package offers a convenience method to conditionally render that text:

	<html><head></head><body><div>
	<form action="/" method="POST">
		<% form = request.pyramid_formencode_classic.get_form() %>
		${form.html_error_placeholder()|n}
		<input type="text" name="email" value="" />
		<input type="text" name="username" value="" />
	</form>
	</div></body></html>


If the marking is not in your template, it will be at the top of the document (before the html), after being run through the `auto_error_formatter`

    <!-- for: Error_Main -->
    <span class="error-message">${error_main}</span>


# Examples

## Usage Overview


### define your form

    import formencode


    class _Schema_Base(formencode.Schema):
        allow_extra_fields = True
        filter_extra_fields = False


    class FormLogin(_Schema_Base):
        email_address = formencode.validators.Email(not_empty=True)
        password = formencode.validators.UnicodeString(not_empty=True)
        remember_me = formencode.validators.Bool()


### define your view/handler


    import pyramid_formencode_classic as formhandling


    class WebLogin(base):

        def login(self):
            if 'login' in self.request.POST:
                return self._login_submit()
            return self._login_print()

        def _login_print(self):
            return render_to_response("web/account/login.mako", {}, self.request)

        def _login_submit(self):

            try:
                (result,
                 formStash
                 ) = formhandling.form_validate(self.request,
                								schema=forms.FormLogin,
                								error_main="There was an error with your form.",
                								)
                if not result:
                    # `formStash.fatal_form(message)` will raise `formhandling.FormInvalid(message)`
                    formStash.fatal_form("Invalid Form")

                results = formStash.results

                useraccount = model.find_user(results['email_address'])
                if not useraccount:
                	# set a custom error and raise an exception to reprint
                    # `formStash.fatal_field(` will raise `formhandling.FormInvalid(`
                    formStash.fatal_field(field="email_address",
										  message="Email not registered",
										  )

                if not useraccount.verify_submitted_password(results['password']):
                	# set a custom error and raise an exception to reprint
                    # `formStash.fatal_field(` will raise `formhandling.FormInvalid(`
                    formStash.fatal_field(field="email_address",
										  message="Wrong password",
										  )

				do_login()
				return HTTPFound(location='/account/home')

            except formhandling.FormInvalid as exc:
                # our reprint logic
                return formhandling.form_reprint(self.request,
                								 self._login_print
                								 )


Twitter Bootstrap Example
=========================

    To handle  twitter bootstrap style errors, it's a bit more manual work -- but doable

        Mako:
            <% form= request.pyramid_formencode_classic.get_form() %>
            ${form.html_error_placeholder()|n}
            <div class="control-group ${form.css_error('email_address')}">
                <label class="control-label" for="email_address">Email</label>
                <input id="email_address" name="email_address" placeholder="Email Address" size="30" type="text" />
                ${form.html_error('email_address')|n}
            </div>

            you could also show an error with:
                % if form.has_error('email_address'):
                    <span class="help-inline">${form.get_error('email_address')}</span>
                % endif


        Pyramid:
            text = formhandling.form_reprint(self.request,
            								 self._login_print,
            								 auto_error_formatter=formhandling.formatter_none,
            								 )

    in the above example there are a few things to note:

        1. in the mako template we use `get_form` to pull/create the default formStash object for the request.  You can specify a specific formStash object if you'd like.
        2. a call is made to `form.css_error()` specifying the 'email_address' field.  this would result in the "control-group error" css mix if there is an error in 'email_address'.
        3. We tell pyramid to use 'formhandling.formatter_none' as the error formatter.  This surpresses errors.  We need to do that instead of using custom error formatters, because FormEncode places errors BEFORE the fields, not AFTER.
        4. I've included two methods of presenting field errors.  they are funtinoally the same.
        5. I've used an ErrorMain to show that there are issues on the form - not just a specific field.



## Example Renderings

there is a trivial attempt at multiple form handling - a "form_stash" argument can be used, which will store different "FormStash" wrapped structures in the names provided.

CAVEATS

1. it doesn't support using a "render" on the form object -- it expects forms to be manually coded, and errors to be regexed out via htmlfill. live with it.
2. this REQUIRES one of the following two example scenarios 

Needless to say: this is really nice and clean in the first scenario, and messy in the latter.


### Example Rendering A - `render_to_response`

The form methods always render a response object via `pyramid.renderers.render_to_response`

	class MyView(handler):

		def test(self):
			if 'submit' in self.request.POST:
				return self._test_submit()
			return self._test_print()

		def _test_print(self):
			return render_to_response("/test_form.mako", {}, self.request)

		def _test_submit(self):
			try:
				(result,
				 formStash
				 ) = formhandling.form_validate(self.request,
												schema=forms.FormLogin,
												error_main="Error",
												)
				if not result:
					raise formhandling.FormInvalid()
				userAccount= query_for_useraccount(formStash.results['email'])
				if not userAccount:
					formStash.fatal_field(field='email',
										  message='Invalid Login',
										  )
				...
			except formhandling.FormInvalid:
				# you could set a field manually too
				#formhandling.formerrors_set(field="email", message='missing this field')
				return formhandling.form_reprint(self.request,
												 self._login_print,
												 )


### Example Rendering B - `view_config`

The form methods use a pyramid renderer

	class MyView(handler):

		@view_config(renderer='/test_form.mako')
		def test(self):
			if 'submit' in self.request.POST:
				return self._test_submit()
			return self._test_print()

		def _test_print(self):
			return {"project":"MyApp"}

		def _test_submit(self):
			try:
				(result,
				 formStash
				 ) = formhandling.form_validate(self.request,
												schema=forms.FormLogin,
												error_main="Error",
												)
				if not result:
					raise formhandling.FormInvalid()
				...
			except formhandling.FormInvalid as exc:
				return formhandling.form_reprint(self.request
												 None,
												 render_view=self._test_print,
												 render_view_template="/test_form.mako"
												 )


## Using multiple forms on a page?

In order to handle multiple form reprints correctly you need:

* pyramid_formencode_classic >= 0.2.0
* formencode >= 2.0.0

This functionality is dependent upon a PR which the formencode team was nice enough to accept in their 2.0.0 release.

This can be done in earlier versions, but you must give them each field a unique 'name' and handle them independently.

In earlier versions, reprints of error forms will not work correctly otherwise.

The following example references a unit test for the new functionality which ships with this package

### Multiple forms must be defined in html

The specific forms must be explicitly invoked in the thml

1. note the explicit `form_stash` argument in `request.pyramid_formencode_classic.get_form("a")`
2. the main error placeholder must note the form. e.g. `form.html_error_placeholder(formencode_form="a")`
3. the formfields must specify `data-formencode-form` e.g. `<input type="text" name="username" value="" data-formencode-form="a"/>`

full html example

	<html><head></head><body><div>
	<form action="/a" method="POST">
		<% form = request.pyramid_formencode_classic.get_form("a") %>
		${form.html_error_placeholder(formencode_form="a")|n}
		<input type="text" name="email" value="" data-formencode-form="a"/>
		<input type="text" name="username" value="" data-formencode-form="a"/>
	</form>
	<form action="/b" method="POST">
		<% form = request.pyramid_formencode_classic.get_form("b") %>
		${form.html_error_placeholder(formencode_form="b")|n}
		<input type="text" name="email" value="" data-formencode-form="b"/>
		<input type="text" name="username" value="" data-formencode-form="b"/>
	</form>
	</div></body></html>

### Multiple forms must be processed in Python

the call to `form_validate` must specify the desired `form_stash`

the call to `form_reprint` must specify *BOTH* the desired `form_stash` and `data_formencode_form` (which is used to handle the form attributes)

full python example:

        try:
            (result,
             formStash
             ) = pyramid_formencode_classic.form_validate(self.request,
                                                          schema=Form_EmailUsername,
                                                          form_stash='b',
                                                          error_main="There was an error with your form.",
                                                          **_validate_kwargs
                                                          )
            if not result:
                raise pyramid_formencode_classic.FormInvalid("Custom Main Error")
        except pyramid_formencode_classic.FormInvalid as exc:
            rendered = pyramid_formencode_classic.form_reprint(self.request,
                                                               _print_form_simple,
                                                               form_stash='b',
                                                               data_formencode_form='b',
                                                               **_reprint_kwargs
                                                               )
            return rendered

#### How does it work?

The `form_stash` argument represents the unique `FormStash` object on the `request` (when it is not explicitly provided, it defaults to `_default`)

The `data_formencode_form` argument is passed from `form_reprint` to `formencode.htmlfill`; when provided, `formencode` will ignore tags which don't match the active formencode form's elements.

The HTML form elements are associated with a form via the attribute `data-formencode-form`


## Dealing with unicode markers in errors (Python2)

There is an issue with formencode under Python2, where an error message shows a unicode marker (see https://github.com/formencode/formencode/issues/132) and may appear like `Value must be one of: a; b (not u'c')` instead of `Value must be one of: a; b (not 'c')`.

After much testing, the simplest way to handle this is to detect it in errors and replace it.  A better method would need to be implemented in formencode itself.
	
A quick way to handle this is to define your own implementation of `form_validate` and just use that throughout your project.

For example:

	import pyramid_formencode_classic
	from six import PY2

	def form_validate(request, **kwargs):
		"""
		kwargs
			things of interest...
			is_unicode_params - webob 1.x+ transfers to unicode.
		"""
		if 'is_unicode_params' not in kwargs:
			kwargs['is_unicode_params'] = True
		(result,
		 formStash
		 ) = pyramid_formencode_classic.form_validate(
			request,
			**kwargs
		)
		formStash.html_error_main_template = TEMPLATE_FORMSTASH_ERRORS
		formStash.html_error_placeholder_template = '<form:error name="%s" format="main"/>'
		formStash.html_error_placeholder_form_template = '<form:error name="%(field)s" format="main" data-formencode-form="%(form)s"/>'
		if not result:
			if PY2:
				# there is an issue in formencode under Python2 
				# see: https://github.com/formencode/formencode/issues/132
				for (k, v) in formStash.errors.items():
					if " (not u'" in v:
						formStash.errors[k] = v.replace( " (not u'",  " (not '")
		return (result,
				formStash
				)



# Misc

if possible, use partial forms and not entire html documents.

80% of this code is adapted from Pylons, 20% is outright copy/pasted.

released under the BSD license, as it incorporates some Pylons code (which was BSD)


## Debugtoolbar Support?

Yep. just add to your development.ini

	debugtoolbar.includes = pyramid_formencode_classic_.debugtoolbar

The debugtoolbar will now have a `FormencodeClassic` panel.

The panel shows information such as:

* which forms were processed/setup
* form results (errors, defaults, actual results)
* form schema
* form parsing status
* form configuration


### Are there tests?

Yes. Starting with the `0.2.0` release, there is a full test suite to ensure forms render as expected.


### Versioning Policy

This project using a Semantic Versioning Policy: `Major.Minor.Patch`.

`Major`: significant API changes
`Minor`: backwards incompatible API changes
`Patch`: backwards compatible API changes and bugfixes

The recommended usage is to pin versioning within the `Major.Minor` range:

	pyramid_formencode_classic >=0.4.0, <0.5.0
            

### Why doesn't form_validate` raise an Exception by default?

This design choice was made to allow for scoping within Pyramid apps:

	try:
		(result,
		 formStash
		 ) = form_validate(...)
		if not result:
			raise FormInvalid()
		# do stuff

	except FormInvalid as exc:
		# formStash is scoped here

An alternative would be something like this...

	try:
		formStash = form_validate(..., form_stash='FormId')
		# do stuff

	except FormInvalid as exc:
		formStash = request.pyramid_formencode_classic['FormId']

The latter situation can be easily accomplished by defining a custom `form_validate` function
		




## Migration Guide

### v0.1.x to v0.2.0

There are some slight changes:

`formStash.html_error_main()` was implemented poorly and rendered the actual template.  a new, better, approach is to use `formStash.html_error_placeholder()`.  if you want the previous behavior, use `formStash.render_html_error_main()`

instead of manually adding a form object, you now can/should use `config.include('pyramid_formencode_classic')` in your app's initialization.

several functions and kwargs were removed, CHANGES provides a full list but highlights include:

* camelCase methods have been removed.
* `section` no longer works as a kwarg. use `field` instead.
* the kwarg `raise_field_invalid` is removed in favor of `raise_FieldInvalid`
* the kwarg `raise_form_invalid` is removed in favor of `raise_FormInvalid`

The new setup makes invoking error formatters for htmlfill much easier.

### v0.3.x to v0.4.x

* `FormStash.set_error()` the `raise_FieldInvalid` kwarg was removed. instead, use `FormStash.fatal_field()`
* `FormStash.set_error()` the `raise_FormInvalid` kwarg was removed. instead, use `FormStash.fatal_form()`
* import formatters from `pyramid_formencode_classic.formatters` not the main namespace





            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/jvanasco/pyramid_formencode_classic",
    "name": "pyramid-formencode-classic",
    "maintainer": null,
    "docs_url": null,
    "requires_python": null,
    "maintainer_email": null,
    "keywords": null,
    "author": "Jonathan Vanasco",
    "author_email": "jonathan@findmeon.com",
    "download_url": "https://files.pythonhosted.org/packages/f0/93/76a5e8bb42ec79b00953c1bce2198288650f9cd0503ad14c040b79c78728/pyramid_formencode_classic-0.6.0.tar.gz",
    "platform": null,
    "description": "![Python package](https://github.com/jvanasco/pyramid_formencode_classic/workflows/Python%20package/badge.svg)\n\n## Current Recommended Version\n\n`v 0.6.0 (2024.04.09)`\n\nThis is backwards compatible due to the following change:\n\n    * `form_validate` now requires `return_stash=True` - which is enabled by default.\n      To support `return_stash=False`, please call `form_validate_simple` instead.\n      This change was required to streamline typing and better standardize the library.\n      Background:\n        `form_validate` was renamed to `_form_validate_core`\n        the new `form_validate` ensures `return_stash==True` and invokes `_form_validate_core`\n        the new `form_validate_simple` ensures `return_stash==False` and invokes `_form_validate_core`\n\n## Current EOL version\n\n`v 0.5.0 (2023.06.xx)`\n\nNew Features:\n* mypy support\n* drop python2\n* project reorganized\n\n\n## Last Python2 Version\n\n`v 0.4.4 (2020.10.20)`\n\nNew Features:\n\n* simplified api\n* bug fixes, better integration\n* python3 support\n* pyramid debugtoolbar support\n* automatic handling of edge cases with ForEach validator errors\n\n\n### Backwards Compatible?\n\n### 0.6.0\n\nA backwards incompatible change was introduced.\n\nform_validate` now requires `return_stash=True` - which is enabled by default.\nTo support `return_stash=False`, please call `form_validate_simple` instead.\n\n### 0.5.0\n\nThe library was reorganized to implement typing.\nMost uses should be backwards compatible.\nIt is possible an object you used moved. If so, please file a ticket to see if we can pull it back in.\nThe default values were moved to a separate file. Those should never have been referenced in code.\n\n\n### 0.4.0\n\n* `0.3.x` No\n\n### 0.3.0\n\n* `0.2.x` Yes.\n* `0.1.x` No. Some functionality switched between `0.1.10` and `0.2.0` and light editing is required.  See `CHANGES.txt` for full details.  Also see the migration guide below.\n\n\n## Installation\n\nThis requires the 2.0 branch of formencode, which has still been an alpha release since Aug 9, 2016\n\n\tpip install formencode==2.0.0a1\n\tpip install pyramid_formencode_classic\n\n\n## What is this package?\n\n`pyramid_formencode_classic` is a port of some classic `Pylons` form validation concepts onto the `Pyramid` framework.\n\nThrough the version `0.1.x` releases, this\u00a0package sought to integrate the Pylons validation onto Pyramid so projects could be ported easier.\n\nStarting with version `0.2.0` strict backwards compatibility has been lost in favor of performance enhancements and streamlining the API. There were simply a handful of bugs and oddities which were not easily fixed.\n\n## How does this handle form validation?\n\nIn the example below, form validation is broken into 4 components:\n\n* A `formencode` form schema\n* a callable dispatcher (`login`)\n* a private printer (`_login_print`)\n* a private submit processor (`_login_submit`)\n\nThe formencode schema does not interact with the database. It is used only for \"lightweight\" validation and cheap operations (length, presence, etc).\n\nThe more involved operations, such as checking a database within a transaction, can occur in the submit step.  \n\nIn this pattern, if an error is encountered at any time, a `FormInvalid` error can be raised to trigger `form_reprint`.  That function will render the template using `Pyramid`'s mechanism and then run `formencode`'s `htmlfill` on it.\n\nIf you want to set a global \"oh noes!\" message for the form, set an error on a special non-existent field like `Error_Main`.\n\n\n## Pyramid Integration\n\nJust do this...\n\n\tconfig.include('pyramid_formencode_classic')\n\t\nWhich will invoke `Pyramid`'s `add_request_method` to add a new attribute to your request.\n\n`request.pyramid_formencode_classic` will be a per-request instance of `pyramid_formencode_classic.FormStashList`.\n\nParsing a form will manage the formdata in `request.pyramid_formencode_classic['_default']` the default form stash.\n\nIf you want to specify a particular stash, because you use multiple forms on a page or have other needs:\n\n* `request.pyramid_formencode_classic.get_form(...)` accepts a `form_stash` kwarg, which defaults to `_default`\n* `form_validate(...)` accepts a `form_stash` kwarg, which defaults to `_default`\n* `form_reprint(...)` accepts a `form_stash` kwarg, which defaults to `_default`\n\n\n## Caveats, Oddities, Etc\n\n### Custom Errors, Custom Error Displays and Missing Fields\n\n#### Where are errors placed?  What about missing fields?\n\n`formencode.htmlfill` prefers to upgrade a html form element with the error information.\n\nIf the html input for an error is missing, such as a custom `Error_Main` field, `formencode` will attempt to do two things:\n\n1. `formencode` will look for a custom `form:error` field, such as `<form:error name=\"Error_Main\"/>`.\n2. If no fields are available, `formencode` will *PREPEND* the error messages to the document.  This can create problems if you are running the reprint on a full (not partial) html page.\n\n#### How are errors styled?\n\n`formencode` styles errors using two commandline arguments.\n\n* `auto_error_formatter` is a function that formats the error messages for fields which do not appear on the document and are pre-pended.\n* `error_formatters` is a dict of error formatters that can be passed into `htmlfill`.  if provided, these will be merged into the htmlfill defaults.\n\n`htmlfill` allows a bit of customization by supporting a `format` attribute in `<form:error/>` declarations, which will invoke the respective entry in the `error_formatters` dict.\n\n#### How can a \"global\" form error be handled?\n\nHandling a custom error can be achieved by reserving a special `error_main` key. By default, `pyramid_formencode_classic` uses `Error_Main`.\n\nOnce you set that field as a form error,  `formencode.htmlfill` will replace this markup in your template\n\n    <form:error name=\"Error_Main\"/>\n\nwith the following html:\n\n    <!-- for: Error_Main -->\n\t<span class=\"error-message\">%(Error_Main)s</span><br/>\n\nIn which the `Error_main` text has been run through `error_formatters['default']`\n\nThere is a small caveat:\n\nIn order for the text to appear in the form where you wish, you must write `<form:error name=\"Error_Main\"/>` in the form.  Non-error views will contain that text in the html source, but not render it; error views will replace it with properly formatted errors.\n\nThis package offers a convenience method to conditionally render that text:\n\n\t<html><head></head><body><div>\n\t<form action=\"/\" method=\"POST\">\n\t\t<% form = request.pyramid_formencode_classic.get_form() %>\n\t\t${form.html_error_placeholder()|n}\n\t\t<input type=\"text\" name=\"email\" value=\"\" />\n\t\t<input type=\"text\" name=\"username\" value=\"\" />\n\t</form>\n\t</div></body></html>\n\n\nIf the marking is not in your template, it will be at the top of the document (before the html), after being run through the `auto_error_formatter`\n\n    <!-- for: Error_Main -->\n    <span class=\"error-message\">${error_main}</span>\n\n\n# Examples\n\n## Usage Overview\n\n\n### define your form\n\n    import formencode\n\n\n    class _Schema_Base(formencode.Schema):\n        allow_extra_fields = True\n        filter_extra_fields = False\n\n\n    class FormLogin(_Schema_Base):\n        email_address = formencode.validators.Email(not_empty=True)\n        password = formencode.validators.UnicodeString(not_empty=True)\n        remember_me = formencode.validators.Bool()\n\n\n### define your view/handler\n\n\n    import pyramid_formencode_classic as formhandling\n\n\n    class WebLogin(base):\n\n        def login(self):\n            if 'login' in self.request.POST:\n                return self._login_submit()\n            return self._login_print()\n\n        def _login_print(self):\n            return render_to_response(\"web/account/login.mako\", {}, self.request)\n\n        def _login_submit(self):\n\n            try:\n                (result,\n                 formStash\n                 ) = formhandling.form_validate(self.request,\n                \t\t\t\t\t\t\t\tschema=forms.FormLogin,\n                \t\t\t\t\t\t\t\terror_main=\"There was an error with your form.\",\n                \t\t\t\t\t\t\t\t)\n                if not result:\n                    # `formStash.fatal_form(message)` will raise `formhandling.FormInvalid(message)`\n                    formStash.fatal_form(\"Invalid Form\")\n\n                results = formStash.results\n\n                useraccount = model.find_user(results['email_address'])\n                if not useraccount:\n                \t# set a custom error and raise an exception to reprint\n                    # `formStash.fatal_field(` will raise `formhandling.FormInvalid(`\n                    formStash.fatal_field(field=\"email_address\",\n\t\t\t\t\t\t\t\t\t\t  message=\"Email not registered\",\n\t\t\t\t\t\t\t\t\t\t  )\n\n                if not useraccount.verify_submitted_password(results['password']):\n                \t# set a custom error and raise an exception to reprint\n                    # `formStash.fatal_field(` will raise `formhandling.FormInvalid(`\n                    formStash.fatal_field(field=\"email_address\",\n\t\t\t\t\t\t\t\t\t\t  message=\"Wrong password\",\n\t\t\t\t\t\t\t\t\t\t  )\n\n\t\t\t\tdo_login()\n\t\t\t\treturn HTTPFound(location='/account/home')\n\n            except formhandling.FormInvalid as exc:\n                # our reprint logic\n                return formhandling.form_reprint(self.request,\n                \t\t\t\t\t\t\t\t self._login_print\n                \t\t\t\t\t\t\t\t )\n\n\nTwitter Bootstrap Example\n=========================\n\n    To handle  twitter bootstrap style errors, it's a bit more manual work -- but doable\n\n        Mako:\n            <% form= request.pyramid_formencode_classic.get_form() %>\n            ${form.html_error_placeholder()|n}\n            <div class=\"control-group ${form.css_error('email_address')}\">\n                <label class=\"control-label\" for=\"email_address\">Email</label>\n                <input id=\"email_address\" name=\"email_address\" placeholder=\"Email Address\" size=\"30\" type=\"text\" />\n                ${form.html_error('email_address')|n}\n            </div>\n\n            you could also show an error with:\n                % if form.has_error('email_address'):\n                    <span class=\"help-inline\">${form.get_error('email_address')}</span>\n                % endif\n\n\n        Pyramid:\n            text = formhandling.form_reprint(self.request,\n            \t\t\t\t\t\t\t\t self._login_print,\n            \t\t\t\t\t\t\t\t auto_error_formatter=formhandling.formatter_none,\n            \t\t\t\t\t\t\t\t )\n\n    in the above example there are a few things to note:\n\n        1. in the mako template we use `get_form` to pull/create the default formStash object for the request.  You can specify a specific formStash object if you'd like.\n        2. a call is made to `form.css_error()` specifying the 'email_address' field.  this would result in the \"control-group error\" css mix if there is an error in 'email_address'.\n        3. We tell pyramid to use 'formhandling.formatter_none' as the error formatter.  This surpresses errors.  We need to do that instead of using custom error formatters, because FormEncode places errors BEFORE the fields, not AFTER.\n        4. I've included two methods of presenting field errors.  they are funtinoally the same.\n        5. I've used an ErrorMain to show that there are issues on the form - not just a specific field.\n\n\n\n## Example Renderings\n\nthere is a trivial attempt at multiple form handling - a \"form_stash\" argument can be used, which will store different \"FormStash\" wrapped structures in the names provided.\n\nCAVEATS\n\n1. it doesn't support using a \"render\" on the form object -- it expects forms to be manually coded, and errors to be regexed out via htmlfill. live with it.\n2. this REQUIRES one of the following two example scenarios \n\nNeedless to say: this is really nice and clean in the first scenario, and messy in the latter.\n\n\n### Example Rendering A - `render_to_response`\n\nThe form methods always render a response object via `pyramid.renderers.render_to_response`\n\n\tclass MyView(handler):\n\n\t\tdef test(self):\n\t\t\tif 'submit' in self.request.POST:\n\t\t\t\treturn self._test_submit()\n\t\t\treturn self._test_print()\n\n\t\tdef _test_print(self):\n\t\t\treturn render_to_response(\"/test_form.mako\", {}, self.request)\n\n\t\tdef _test_submit(self):\n\t\t\ttry:\n\t\t\t\t(result,\n\t\t\t\t formStash\n\t\t\t\t ) = formhandling.form_validate(self.request,\n\t\t\t\t\t\t\t\t\t\t\t\tschema=forms.FormLogin,\n\t\t\t\t\t\t\t\t\t\t\t\terror_main=\"Error\",\n\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\tif not result:\n\t\t\t\t\traise formhandling.FormInvalid()\n\t\t\t\tuserAccount= query_for_useraccount(formStash.results['email'])\n\t\t\t\tif not userAccount:\n\t\t\t\t\tformStash.fatal_field(field='email',\n\t\t\t\t\t\t\t\t\t\t  message='Invalid Login',\n\t\t\t\t\t\t\t\t\t\t  )\n\t\t\t\t...\n\t\t\texcept formhandling.FormInvalid:\n\t\t\t\t# you could set a field manually too\n\t\t\t\t#formhandling.formerrors_set(field=\"email\", message='missing this field')\n\t\t\t\treturn formhandling.form_reprint(self.request,\n\t\t\t\t\t\t\t\t\t\t\t\t self._login_print,\n\t\t\t\t\t\t\t\t\t\t\t\t )\n\n\n### Example Rendering B - `view_config`\n\nThe form methods use a pyramid renderer\n\n\tclass MyView(handler):\n\n\t\t@view_config(renderer='/test_form.mako')\n\t\tdef test(self):\n\t\t\tif 'submit' in self.request.POST:\n\t\t\t\treturn self._test_submit()\n\t\t\treturn self._test_print()\n\n\t\tdef _test_print(self):\n\t\t\treturn {\"project\":\"MyApp\"}\n\n\t\tdef _test_submit(self):\n\t\t\ttry:\n\t\t\t\t(result,\n\t\t\t\t formStash\n\t\t\t\t ) = formhandling.form_validate(self.request,\n\t\t\t\t\t\t\t\t\t\t\t\tschema=forms.FormLogin,\n\t\t\t\t\t\t\t\t\t\t\t\terror_main=\"Error\",\n\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\tif not result:\n\t\t\t\t\traise formhandling.FormInvalid()\n\t\t\t\t...\n\t\t\texcept formhandling.FormInvalid as exc:\n\t\t\t\treturn formhandling.form_reprint(self.request\n\t\t\t\t\t\t\t\t\t\t\t\t None,\n\t\t\t\t\t\t\t\t\t\t\t\t render_view=self._test_print,\n\t\t\t\t\t\t\t\t\t\t\t\t render_view_template=\"/test_form.mako\"\n\t\t\t\t\t\t\t\t\t\t\t\t )\n\n\n## Using multiple forms on a page?\n\nIn order to handle multiple form reprints correctly you need:\n\n* pyramid_formencode_classic >= 0.2.0\n* formencode >= 2.0.0\n\nThis functionality is dependent upon a PR which the formencode team was nice enough to accept in their 2.0.0 release.\n\nThis can be done in earlier versions, but you must give them each field a unique 'name' and handle them independently.\n\nIn earlier versions, reprints of error forms will not work correctly otherwise.\n\nThe following example references a unit test for the new functionality which ships with this package\n\n### Multiple forms must be defined in html\n\nThe specific forms must be explicitly invoked in the thml\n\n1. note the explicit `form_stash` argument in `request.pyramid_formencode_classic.get_form(\"a\")`\n2. the main error placeholder must note the form. e.g. `form.html_error_placeholder(formencode_form=\"a\")`\n3. the formfields must specify `data-formencode-form` e.g. `<input type=\"text\" name=\"username\" value=\"\" data-formencode-form=\"a\"/>`\n\nfull html example\n\n\t<html><head></head><body><div>\n\t<form action=\"/a\" method=\"POST\">\n\t\t<% form = request.pyramid_formencode_classic.get_form(\"a\") %>\n\t\t${form.html_error_placeholder(formencode_form=\"a\")|n}\n\t\t<input type=\"text\" name=\"email\" value=\"\" data-formencode-form=\"a\"/>\n\t\t<input type=\"text\" name=\"username\" value=\"\" data-formencode-form=\"a\"/>\n\t</form>\n\t<form action=\"/b\" method=\"POST\">\n\t\t<% form = request.pyramid_formencode_classic.get_form(\"b\") %>\n\t\t${form.html_error_placeholder(formencode_form=\"b\")|n}\n\t\t<input type=\"text\" name=\"email\" value=\"\" data-formencode-form=\"b\"/>\n\t\t<input type=\"text\" name=\"username\" value=\"\" data-formencode-form=\"b\"/>\n\t</form>\n\t</div></body></html>\n\n### Multiple forms must be processed in Python\n\nthe call to `form_validate` must specify the desired `form_stash`\n\nthe call to `form_reprint` must specify *BOTH* the desired `form_stash` and `data_formencode_form` (which is used to handle the form attributes)\n\nfull python example:\n\n        try:\n            (result,\n             formStash\n             ) = pyramid_formencode_classic.form_validate(self.request,\n                                                          schema=Form_EmailUsername,\n                                                          form_stash='b',\n                                                          error_main=\"There was an error with your form.\",\n                                                          **_validate_kwargs\n                                                          )\n            if not result:\n                raise pyramid_formencode_classic.FormInvalid(\"Custom Main Error\")\n        except pyramid_formencode_classic.FormInvalid as exc:\n            rendered = pyramid_formencode_classic.form_reprint(self.request,\n                                                               _print_form_simple,\n                                                               form_stash='b',\n                                                               data_formencode_form='b',\n                                                               **_reprint_kwargs\n                                                               )\n            return rendered\n\n#### How does it work?\n\nThe `form_stash` argument represents the unique `FormStash` object on the `request` (when it is not explicitly provided, it defaults to `_default`)\n\nThe `data_formencode_form` argument is passed from `form_reprint` to `formencode.htmlfill`; when provided, `formencode` will ignore tags which don't match the active formencode form's elements.\n\nThe HTML form elements are associated with a form via the attribute `data-formencode-form`\n\n\n## Dealing with unicode markers in errors (Python2)\n\nThere is an issue with formencode under Python2, where an error message shows a unicode marker (see https://github.com/formencode/formencode/issues/132) and may appear like `Value must be one of: a; b (not u'c')` instead of `Value must be one of: a; b (not 'c')`.\n\nAfter much testing, the simplest way to handle this is to detect it in errors and replace it.  A better method would need to be implemented in formencode itself.\n\t\nA quick way to handle this is to define your own implementation of `form_validate` and just use that throughout your project.\n\nFor example:\n\n\timport pyramid_formencode_classic\n\tfrom six import PY2\n\n\tdef form_validate(request, **kwargs):\n\t\t\"\"\"\n\t\tkwargs\n\t\t\tthings of interest...\n\t\t\tis_unicode_params - webob 1.x+ transfers to unicode.\n\t\t\"\"\"\n\t\tif 'is_unicode_params' not in kwargs:\n\t\t\tkwargs['is_unicode_params'] = True\n\t\t(result,\n\t\t formStash\n\t\t ) = pyramid_formencode_classic.form_validate(\n\t\t\trequest,\n\t\t\t**kwargs\n\t\t)\n\t\tformStash.html_error_main_template = TEMPLATE_FORMSTASH_ERRORS\n\t\tformStash.html_error_placeholder_template = '<form:error name=\"%s\" format=\"main\"/>'\n\t\tformStash.html_error_placeholder_form_template = '<form:error name=\"%(field)s\" format=\"main\" data-formencode-form=\"%(form)s\"/>'\n\t\tif not result:\n\t\t\tif PY2:\n\t\t\t\t# there is an issue in formencode under Python2 \n\t\t\t\t# see: https://github.com/formencode/formencode/issues/132\n\t\t\t\tfor (k, v) in formStash.errors.items():\n\t\t\t\t\tif \" (not u'\" in v:\n\t\t\t\t\t\tformStash.errors[k] = v.replace( \" (not u'\",  \" (not '\")\n\t\treturn (result,\n\t\t\t\tformStash\n\t\t\t\t)\n\n\n\n# Misc\n\nif possible, use partial forms and not entire html documents.\n\n80% of this code is adapted from Pylons, 20% is outright copy/pasted.\n\nreleased under the BSD license, as it incorporates some Pylons code (which was BSD)\n\n\n## Debugtoolbar Support?\n\nYep. just add to your development.ini\n\n\tdebugtoolbar.includes = pyramid_formencode_classic_.debugtoolbar\n\nThe debugtoolbar will now have a `FormencodeClassic` panel.\n\nThe panel shows information such as:\n\n* which forms were processed/setup\n* form results (errors, defaults, actual results)\n* form schema\n* form parsing status\n* form configuration\n\n\n### Are there tests?\n\nYes. Starting with the `0.2.0` release, there is a full test suite to ensure forms render as expected.\n\n\n### Versioning Policy\n\nThis project using a Semantic Versioning Policy: `Major.Minor.Patch`.\n\n`Major`: significant API changes\n`Minor`: backwards incompatible API changes\n`Patch`: backwards compatible API changes and bugfixes\n\nThe recommended usage is to pin versioning within the `Major.Minor` range:\n\n\tpyramid_formencode_classic >=0.4.0, <0.5.0\n            \n\n### Why doesn't form_validate` raise an Exception by default?\n\nThis design choice was made to allow for scoping within Pyramid apps:\n\n\ttry:\n\t\t(result,\n\t\t formStash\n\t\t ) = form_validate(...)\n\t\tif not result:\n\t\t\traise FormInvalid()\n\t\t# do stuff\n\n\texcept FormInvalid as exc:\n\t\t# formStash is scoped here\n\nAn alternative would be something like this...\n\n\ttry:\n\t\tformStash = form_validate(..., form_stash='FormId')\n\t\t# do stuff\n\n\texcept FormInvalid as exc:\n\t\tformStash = request.pyramid_formencode_classic['FormId']\n\nThe latter situation can be easily accomplished by defining a custom `form_validate` function\n\t\t\n\n\n\n\n## Migration Guide\n\n### v0.1.x to v0.2.0\n\nThere are some slight changes:\n\n`formStash.html_error_main()` was implemented poorly and rendered the actual template.  a new, better, approach is to use `formStash.html_error_placeholder()`.  if you want the previous behavior, use `formStash.render_html_error_main()`\n\ninstead of manually adding a form object, you now can/should use `config.include('pyramid_formencode_classic')` in your app's initialization.\n\nseveral functions and kwargs were removed, CHANGES provides a full list but highlights include:\n\n* camelCase methods have been removed.\n* `section` no longer works as a kwarg. use `field` instead.\n* the kwarg `raise_field_invalid` is removed in favor of `raise_FieldInvalid`\n* the kwarg `raise_form_invalid` is removed in favor of `raise_FormInvalid`\n\nThe new setup makes invoking error formatters for htmlfill much easier.\n\n### v0.3.x to v0.4.x\n\n* `FormStash.set_error()` the `raise_FieldInvalid` kwarg was removed. instead, use `FormStash.fatal_field()`\n* `FormStash.set_error()` the `raise_FormInvalid` kwarg was removed. instead, use `FormStash.fatal_form()`\n* import formatters from `pyramid_formencode_classic.formatters` not the main namespace\n\n\n\n\n",
    "bugtrack_url": null,
    "license": "BSD",
    "summary": "('An implementation of the classic Pylons formencode validation, for Pyramid',)",
    "version": "0.6.0",
    "project_urls": {
        "Homepage": "https://github.com/jvanasco/pyramid_formencode_classic"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f09376a5e8bb42ec79b00953c1bce2198288650f9cd0503ad14c040b79c78728",
                "md5": "738b1a71e150ff7fc287ad73a8317a18",
                "sha256": "1a652b3b343acf6f1e15c653aa33e669802d69c079db5160ac8032d690e7ddcf"
            },
            "downloads": -1,
            "filename": "pyramid_formencode_classic-0.6.0.tar.gz",
            "has_sig": false,
            "md5_digest": "738b1a71e150ff7fc287ad73a8317a18",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 37231,
            "upload_time": "2024-04-09T20:25:02",
            "upload_time_iso_8601": "2024-04-09T20:25:02.995457Z",
            "url": "https://files.pythonhosted.org/packages/f0/93/76a5e8bb42ec79b00953c1bce2198288650f9cd0503ad14c040b79c78728/pyramid_formencode_classic-0.6.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-04-09 20:25:02",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "jvanasco",
    "github_project": "pyramid_formencode_classic",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "tox": true,
    "lcname": "pyramid-formencode-classic"
}
        
Elapsed time: 0.21156s