Package: pyramid-formencode-classic
Current Recommended Versionv 0.5.0 (2023.06.xx)New Features:mypy supportdrop python2project reorganizedLast Python2 Versionv 0.4.4 (2020.10.20)New Features:simplified apibug fixes, better integrationpython3 supportpyramid debugtoolbar supportautomatic handling of edge cases with ForEach validator errorsBackwards Compatible?0.5.0The 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.00.3.xNo0.3.00.2.xYes.0.1.xNo. Some functionality switched between0.1.10and0.2.0and light editing is required.  SeeCHANGES.txtfor full details.  Also see the migration guide below.InstallationThis requires the 2.0 branch of formencode, which has still been an alpha release since Aug 9, 2016pip install formencode==2.0.0a1
pip install pyramid_formencode_classicWhat is this package?pyramid_formencode_classicis a port of some classicPylonsform validation concepts onto thePyramidframework.Through the version0.1.xreleases, this package sought to integrate the Pylons validation onto Pyramid so projects could be ported easier.Starting with version0.2.0strict 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:Aformencodeform schemaa 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, aFormInvaliderror can be raised to triggerform_reprint.  That function will render the template usingPyramid's mechanism and then runformencode'shtmlfillon it.If you want to set a global "oh noes!" message for the form, set an error on a special non-existent field likeError_Main.Pyramid IntegrationJust do this...config.include('pyramid_formencode_classic')Which will invokePyramid'sadd_request_methodto add a new attribute to your request.request.pyramid_formencode_classicwill be a per-request instance ofpyramid_formencode_classic.FormStashList.Parsing a form will manage the formdata inrequest.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 aform_stashkwarg, which defaults to_defaultform_validate(...)accepts aform_stashkwarg, which defaults to_defaultform_reprint(...)accepts aform_stashkwarg, which defaults to_defaultCaveats, Oddities, EtcCustom Errors, Custom Error Displays and Missing FieldsWhere are errors placed?  What about missing fields?formencode.htmlfillprefers to upgrade a html form element with the error information.If the html input for an error is missing, such as a customError_Mainfield,formencodewill attempt to do two things:formencodewill look for a customform:errorfield, such as<form:error name="Error_Main"/>.If no fields are available,formencodewillPREPENDthe 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?formencodestyles errors using two commandline arguments.auto_error_formatteris a function that formats the error messages for fields which do not appear on the document and are pre-pended.error_formattersis a dict of error formatters that can be passed intohtmlfill.  if provided, these will be merged into the htmlfill defaults.htmlfillallows a bit of customization by supporting aformatattribute in<form:error/>declarations, which will invoke the respective entry in theerror_formattersdict.How can a "global" form error be handled?Handling a custom error can be achieved by reserving a specialerror_mainkey. By default,pyramid_formencode_classicusesError_Main.Once you set that field as a form error,formencode.htmlfillwill 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 theError_maintext has been run througherror_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 theauto_error_formatter<!-- for: Error_Main -->
<span class="error-message">${error_main}</span>ExamplesUsage Overviewdefine your formimport 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/handlerimport 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 ExampleTo 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 Renderingsthere 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.CAVEATSit 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.this REQUIRES one of the following two example scenariosNeedless to say: this is really nice and clean in the first scenario, and messy in the latter.Example Rendering A -render_to_responseThe form methods always render a response object viapyramid.renderers.render_to_responseclass 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_configThe form methods use a pyramid rendererclass 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.0formencode >= 2.0.0This 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 packageMultiple forms must be defined in htmlThe specific forms must be explicitly invoked in the thmlnote the explicitform_stashargument inrequest.pyramid_formencode_classic.get_form("a")the main error placeholder must note the form. e.g.form.html_error_placeholder(formencode_form="a")the formfields must specifydata-formencode-forme.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 Pythonthe call toform_validatemust specify the desiredform_stashthe call toform_reprintmust specifyBOTHthe desiredform_stashanddata_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 renderedHow does it work?Theform_stashargument represents the uniqueFormStashobject on therequest(when it is not explicitly provided, it defaults to_default)Thedata_formencode_formargument is passed fromform_reprinttoformencode.htmlfill; when provided,formencodewill ignore tags which don't match the active formencode form's elements.The HTML form elements are associated with a form via the attributedata-formencode-formDealing with unicode markers in errors (Python2)There is an issue with formencode under Python2, where an error message shows a unicode marker (seehttps://github.com/formencode/formencode/issues/132) and may appear likeValue must be one of: a; b (not u'c')instead ofValue 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 ofform_validateand 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
			)Miscif 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.inidebugtoolbar.includes = pyramid_formencode_classic_.debugtoolbarThe debugtoolbar will now have aFormencodeClassicpanel.The panel shows information such as:which forms were processed/setupform results (errors, defaults, actual results)form schemaform parsing statusform configurationAre there tests?Yes. Starting with the0.2.0release, there is a full test suite to ensure forms render as expected.Versioning PolicyThis project using a Semantic Versioning Policy:Major.Minor.Patch.Major: significant API changesMinor: backwards incompatible API changesPatch: backwards compatible API changes and bugfixesThe recommended usage is to pin versioning within theMajor.Minorrange:pyramid_formencode_classic >=0.4.0, <0.5.0Why 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 hereAn 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 customform_validatefunctionMigration Guidev0.1.x to v0.2.0There are some slight changes:formStash.html_error_main()was implemented poorly and rendered the actual template.  a new, better, approach is to useformStash.html_error_placeholder().  if you want the previous behavior, useformStash.render_html_error_main()instead of manually adding a form object, you now can/should useconfig.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.sectionno longer works as a kwarg. usefieldinstead.the kwargraise_field_invalidis removed in favor ofraise_FieldInvalidthe kwargraise_form_invalidis removed in favor ofraise_FormInvalidThe new setup makes invoking error formatters for htmlfill much easier.v0.3.x to v0.4.xFormStash.set_error()theraise_FieldInvalidkwarg was removed. instead, useFormStash.fatal_field()FormStash.set_error()theraise_FormInvalidkwarg was removed. instead, useFormStash.fatal_form()import formatters frompyramid_formencode_classic.formattersnot the main namespace