# -*- 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'' % 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)