Skip to content

Instantly share code, notes, and snippets.

@jvanasco
Created February 4, 2012 01:18
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jvanasco/1734244 to your computer and use it in GitHub Desktop.
Save jvanasco/1734244 to your computer and use it in GitHub Desktop.
pyramid_formencode_classic.py
"""
v 0.0.3
a port of some classic pylons styling, but without much of the cruft that was not used often
This allows for a very particular coding style, which i prefer.
As you can see below, 'methods' are broken into multiple parts:
- 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 entirely for "lightweight" validation and cheap operations ( length, presence, etc )
The more involved operations occur in the submit processor.
At any time, if an error is occured, a call to "form_reprint" can be made. that function makes a subrequest and runs htmlfill on it.
Custom errors can be set as well.
If you want to set a "oh noes! message" for the form, pass in the `error_main` argument to validate, that will set an error in Error_Main , which will do one of two things:
a- formencode.htmlfill will replace this marking in your template
<form:error name="Error_Main"/>
with the follwing :
<span class="error-message">${error_main}</span><br/>
caveat:
<form:error name="Error_Main"/> will appear on valid documents , as htmlfill won't be called to strip it out
if you want to strip it out, you could do the following :
- pass your formStash into a template via the print mechanism
- test for form validity in the form ; the FormStash class has an is_error attribute which is set True on errors ( and cleared when no errors exist )
b- if the marking is not in your template, it will be at the top of the document ( before the html ) as
<!-- for: Error_Main -->
<span class="error-message">${error_main}</span>
As with all formencode implementaitons, you can control where an error message appears by placing an explicit <form:error name="${formfield}"/>
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.
the caveat to this approach right now, is that it doesn't support using a "render" on the form object -- it expects forms to be manually coded. this was originally by design ( to have more control ). I'd love to be able to support that - or find other ways to allow the subrequest mechanism to go away.
80% of this code is adapted from Pylons, 20% is outright copy/pasted.
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
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" , {"project":"MyApp" } , self.request)
def _login_submit(self):
try :
if not pyramid_formencode_classic.form_validate( self.request , schema=forms.FormLogin , error_main="OH NOES!" ):
raise pyramid_formencode_classic.ValidationStop("Invalid Form")
results= self.request.formStash.results
useraccount = model.find_user( results['email_address'] )
if not useraccount:
pyramid_formencode_classic.formerrors_set(self.request,section="email_address",message="Email not registered")
if not useraccount.verify_submitted_password( results['password'] ):
pyramid_formencode_classic.formerrors_set(self.request,section="email_address",message="Wrong password")
except pyramid_formencode_classic.ValidationStop :
pyramid_formencode_classic.formerrors_set(self.request,section="Error_Main",message="There was an error with your form")
return formhandling.form_reprint( self.request , self._login_print )
except:
raise
h.do_login()
return HTTPFound(location='/account/home')
"""
import logging
log = logging.getLogger(__name__)
import formencode
import sys
def determine_response_charset(response):
"""FROM PYLONS -- Determine the charset of the specified Response object,
returning the default system encoding when none is set"""
charset = response.charset
if charset is None:
charset = sys.getdefaultencoding()
log.debug("Determined result charset to be: %s", charset)
return charset
def encode_formencode_errors(errors, encoding, encoding_errors='strict'):
"""FROM PYLONS -- Encode any unicode values contained in a FormEncode errors dict
to raw strings of the specified encoding"""
if errors is None or isinstance(errors, str):
# None or Just incase this is FormEncode<=0.7
pass
elif isinstance(errors, unicode):
errors = errors.encode(encoding, encoding_errors)
elif isinstance(errors, dict):
for key, value in errors.iteritems():
errors[key] = encode_formencode_errors(value, encoding, encoding_errors)
else:
# Fallback to an iterable (a list)
errors = [encode_formencode_errors(error, encoding, encoding_errors) for error in errors]
return errors
def formatter_nobr(error):
"""
Formatter that escapes the error, wraps the error in a span with
class ``error-message``, and doesn't add a ``<br>``
"""
return '<span class="error-message">%s</span>\n' % formencode.rewritingparser.html_quote(error)
class ValidationStop(Exception):
"""Stop validating"""
pass
class FormStash( object ):
"""Wrapper object, stores all the vars and objects surrounding a form validation"""
is_error= None
is_parsed= False
is_unicode_params= False
schema= None
errors= None
results= None
defaults= None
def __init__(self):
self.errors= {}
self.results= {}
self.defaults= {}
def get_form( request, form_stash='formStash' ):
return getattr( request , form_stash )
def _form_ensure( request , form_stash='formStash' ):
"""ensures there is a FormStash instance attached to the request"""
if not hasattr( request , form_stash):
setattr( request , form_stash , FormStash() )
return getattr( request , form_stash )
def formerrors_set( request , form_stash='formStash' , section='Error_Main' , message='There was an error with your submission...' ):
"""manages the dict of errors"""
form= _form_ensure( request , form_stash=form_stash )
if message is None:
if section in form.errors:
del form.errors[section]
else:
form.errors[section]= message
if form.errors:
form.is_error = True
def formerrors_clear( request, form_stash='formStash' , section=None ):
form= _form_ensure( request , form_stash=form_stash )
if form.errors :
if section:
if section in form.errors :
del form.errors['section']
else:
form.errors= {}
if form.errors:
form.is_error = True
def form_validate(\
request ,
schema=None ,
form_stash= 'formStash',
validate_post=True ,
validate_get=False ,
validate_params=None ,
variable_decode=False ,
dict_char='.' ,
list_char='-' ,
state= None,
error_main=None ,
):
"""form validation only : returns True/False ; sets up Errors ;
Validate input for a FormEncode schema.
This is largely ported Pylons core, as is all of the docstring!
Given a form schema, validate will attempt to validate the schema
If validation was successful, the valid result dict will be saved as ``request.formResult.results``.
.. warnings ::
``validate_post`` and ``validate_get`` applies to *where* the arguments to be
validated come from. It does *not* restrict the form to
only working with post, merely only checking POST vars.
``validate_params`` will exclude get and post data
``schema`` ( None )
Refers to a FormEncode Schema object to use during validation.
``form_stash`` ( formStash )
Name of the attribute formStash will be saved into.
Useful if you have multiple forms.
``validate_post`` ( True )
Boolean that indicates whether or not POST variables
should be included during validation.
``validate_get`` ( True )
Boolean that indicates whether or not GET variables
should be included during validation.
``validate_params``
MultiDict of params if you want to validate by hand
``variable_decode`` ( False )
Boolean to indicate whether FormEncode's variable decode
function should be run on the form input before validation.
``dict_char`` ( '.' )
Passed through to FormEncode. Toggles the form field naming
scheme used to determine what is used to represent a dict. This
option is only applicable when used with variable_decode=True.
``list_char`` ( '-' )
Passed through to FormEncode. Toggles the form field naming
scheme used to determine what is used to represent a list. This
option is only applicable when used with variable_decode=True.
``state``
Passed through to FormEncode for use in validators that utilize a state object.
``error_main`` ( None )
If there are any errors that occur, this will drop an error in "Error_Main" key.
"""
log.debug("form_validate - starting...")
errors= {}
formStash= FormStash()
formStash.schema= schema
try:
# if we don't pass in ``validate_params``...
## we must validate via GET, POST or BOTH
if not validate_params:
if validate_post and validate_get :
validate_params = request.params
elif validate_post and not validate_get :
validate_params = request.POST
elif not validate_post and validate_get :
validate_params = request.GET
# if there are no params to validate against, then just stop
if not validate_params:
formStash.is_error= True
raise ValidationStop()
# initialize our results
results= {}
if schema:
log.debug("form_validate - validating against a schema")
try:
results= schema.to_python( validate_params , state )
except formencode.Invalid, e:
errors= e.unpack_errors( variable_decode , dict_char , list_char )
formStash.is_parsed= True
formStash.results= results
formStash.errors= errors
formStash.defaults = validate_params
if errors:
log.debug("form_validate - Errors found in validation")
formStash.is_error= True
if error_main:
formerrors_set( request , section='Error_Main', message=error_main )
except ValidationStop :
log.debug("form_validate - encountered a ValidationStop")
pass
setattr( request , form_stash, formStash )
return not formStash.is_error
def form_reprint( request , form_print_method , form_stash='formStash', auto_error_formatter=formatter_nobr , **htmlfill_kwargs ):
"""reprint a form
args:
``request`` -- request instance
``form_print_method`` -- bound method to execute
kwargs:
``frorm_stash`` (formStash) -- specify a stash
``auto_error_formatter`` (formatter_nobr) -- specify a formatter for rendering errors
this is an htmlfill_kwargs , but we default to one without a br
`**htmlfill_kwargs` -- passed on to htmlfill
"""
log.debug("form_reprint - starting...")
response= form_print_method()
# If the form_content is an exception response, return it
# potential ways to check:
## hasattr( response, 'exception')
## resposne.code != 200
## repsonse.code == 302 <-- http found
if hasattr(response, 'exception'):
log.debug("form_reprint - response has exception, redirecting")
return response
formStash= getattr( request , form_stash )
form_content= response.text
# Ensure htmlfill can safely combine the form_content, params and
# errors variables (that they're all of the same string type)
if not formStash.is_unicode_params:
log.debug("Raw string form params: ensuring the '%s' form and FormEncode errors are converted to raw strings for htmlfill", form_print_method )
encoding= determine_response_charset(response)
if hasattr( response , 'errors' ):
# WSGIResponse's content may (unlikely) be unicode
if isinstance(form_content, unicode):
form_content= form_content.encode(encoding, response.errors)
# FormEncode>=0.7 errors are unicode (due to being localized via ugettext). Convert any of the possible formencode unpack_errors formats to contain raw strings
errors= encode_formencode_errors(errors, encoding, response.errors)
elif not isinstance( form_content, unicode):
log.debug("Unicode form params: ensuring the '%s' form is converted to unicode for htmlfill", form)
encoding= determine_response_charset(response)
form_content= form_content.decode(encoding)
form_content= formencode.htmlfill.render(\
form_content,
defaults=formStash.defaults,
errors=formStash.errors,
auto_error_formatter=auto_error_formatter,
**htmlfill_kwargs
)
response.text= form_content
return response
# comment out the above return , and raise a ValueError to debug the local environment with the Pyramid interactive tool
raise ValueError('!!!')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment