scribeengine/scribeengine/lib/validation/__init__.py

178 lines
7.3 KiB
Python

# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
###############################################################################
# ScribeEngine - Open Source Blog Software #
# --------------------------------------------------------------------------- #
# Copyright (c) 2010 Raoul Snyman #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Validation helper classes and methods.
"""
import logging
from formencode.htmlfill import FillingParser
from decorator import decorator
log = logging.getLogger(__name__)
class ClassFillingParser(FillingParser):
"""
This parser inherits from the base htmlfill.FillingParser, but overrides
the behaviour.
If it encounters the class `error_${name}` on an element and the name given
is in the error dictionary, then the `form-error` class is added to the
element. Otherwise, the class is removed.
If it encounters the class `errormessage_${name}` on an element and the name
given is in the error dictionary, then the content of the element is
replaced with the error from the error dictionary. Otherwise, the element
is removed.
The class `error_` (ie, no name) will give any form errors.
"""
def __init__(self, *args, **kwargs):
"""
Set up the filling parser.
"""
self.formname = kwargs.pop(u'formname', None)
self.in_errormessage = None
return FillingParser.__init__(self, *args, **kwargs)
def handle_starttag(self, tag, attrs, startend=False):
"""
Handle the start of an HTML tag.
"""
# Find any class attribute on the element
class_attr = None
for key, value in attrs:
if key == u'class':
class_attr = value
break
# Only have to handle the case where there is a class.
if class_attr:
classes = class_attr.split(u' ')
for class_name in classes:
# Replace error_${name} with "error" if name is in error dict
if class_name.startswith(u'error_'):
classes = [cls for cls in classes if cls != class_name]
field = class_name[6:]
if field in self.errors:
classes.append(u'form-error')
self.set_attr(attrs, u'class', u' '.join(classes))
self.write_tag(tag, attrs, startend)
self.skip_next = True
return
# Replace the contents of elements with class
# errormessage_${name} with the error from the error
# dictionary (or delete the element entirely if there is no
# such error).
if class_name.startswith(u'errormessage_'):
field = class_name[13:]
self.in_errormessage = tag
self.skip_error = True
# form errors
if not field:
field = None
if field in self.errors:
classes = [cls for cls in classes if cls != class_name]
classes.append(u'form-error')
self.set_attr(attrs, u'class', u' '.join(classes))
self.write_tag(tag, attrs, startend)
self.write_text(htmlliteral(unicode(self.errors[field])).text)
self.write_text(u'</%s>' % tag)
self.skip_next = True
return
return FillingParser.handle_starttag(self, tag, attrs, startend=False)
def handle_endtag(self, tag):
"""
Handle the ending HTML tag.
"""
FillingParser.handle_endtag(self, tag)
# We're handling skipping of contents of an element with
# errormessage_* on it.
#
# After we encounter the end tag, we can stop ignoring elements.
if self.in_errormessage == tag:
self.skip_error = False
self.in_errormessage = None
self.skip_next = True
@classmethod
def html_error_fill(cls, form, errors_and_values):
"""
Create the custom ClassFillingParser, and pass through the values,
errors, and formname from the errors dictionary.
Converts the incoming form from UTF-8-encoded byte strings to Unicode
strings.
"""
p = cls(defaults=errors_and_values[u'form_values'],
errors=errors_and_values[u'form_errors'],
auto_error_formatter=False)
p.feed(form.decode(u'utf-8'))
p.close()
return p.text().encode(u'utf-8')
def jsvalidate(form_id):
"""
This decorator is used to generate JavaScript for client-side validate.
``form_id``
The HTML ``id`` of the form to be validated.
"""
def entangle(func, self, *args, **kwargs):
default_jscript = u"""/* jQuery Validation */
$ProjectHQ.Events.bind_load(function () {
$("#%s").validate({
errorClass: "form-error",
errorContainer: "#form-errors",
errorLabelContainer: "#form-errors > ul",
wrapper: "li",
rules: {
%s
},
messages: {
%s
}
});
});"""
validators = []
messages = []
jsschema = func(self, *args, **kwargs)
for name, validator in jsschema.iteritems():
validators.append(u'%s: {%s}' % (name, validator.to_javascript()))
if validator.get_message() is not None:
validator_message = validator.get_message()
if isinstance(validator_message, basestring):
messages.append(u'%s: "%s"' % (name, validator.get_message()))
else:
validator_messages = []
for key, value in validator_message.iteritems():
validator_messages.append(u'%s: "%s"' % (key, value))
messages.append(u'%s: {\n %s\n }' %
(name, ',\n '.join(validator_messages)))
jscript = default_jscript % (form_id, u',\n '.join(validators),
u',\n '.join(messages))
return jscript
return decorator(entangle)