
These are some formless types I'm using in my app. I think it's a good idea to share it with you, since they can be useful not just to me. The reason of the webform.freeformDefaultContentPattern change is to use div instead of stan, I'm doing some strange stuff, so without div it becomes not compliant html. I need the context as last parameter of the processor handler, not in the below types, but in others. Note also the RequiredChoice, I believe the check "x not in self.choices" is required for security reasons, most people will expect the client does the right thing, but it's not forced to without such check. I only use requiredchoice and never choice. I also wonder if it really worth the new API that slows down it so much with the normal rendering, but let's say that's a more general problem with compy and getinterfaces. Plus the new API is way confusing, integers gets converted to strings and if you conver them back to integers with valueToKey the processor will forget the value, the processor remembers the value only if the coerce returns a string. That might actually be a bug. Anyway the below workaround in my requiredchoice works (i.e. always try to convert to int in the coerce for the security check, but keep returning a string). I only use lists of strings or integers as choices for requiredchoice of course, so I'm fine. class IntegerRange(annotate.Integer): def __init__(self, min = None, max = None, *args, **kw): super(IntegerRange, self).__init__(*args, **kw) self.min = min self.max = max self.requiredFailMessage = 'Please enter an integer between %d and %d.' \ % (self.min, self.max) def coerce(self, val, configurable): v = super(IntegerRange, self).coerce(val, configurable) if v > self.max or v < self.min: raise annotate.InputError("Integer must be between %d and %d." % (self.min, self.max)) return v class RealRange(annotate.Real): def __init__(self, min = None, max = None, *args, **kw): super(RealRange, self).__init__(*args, **kw) self.min = min self.max = max self.requiredFailMessage = 'Please enter a real number between %d and %d.' \ % (self.min, self.max) def coerce(self, val, configurable): v = super(RealRange, self).coerce(val, configurable) if v > self.max or v < self.min: raise annotate.InputError("Real must be between %d and %d." % (self.min, self.max)) return v class MaxlengthString(annotate.String): def __init__(self, *args, **kwargs): if 'maxlength' in kwargs: self.maxlength = kwargs['maxlength'] del kwargs['maxlength'] else: self.maxlength = 10 super(MaxlengthString, self).__init__(*args, **kwargs) def coerce(self, val, configurable): r = super(MaxlengthString, self).coerce(val, configurable) if search_for_invalid_chars.search(r): raise annotate.InputError("%r contains invalid characters." % val) if len(r) > self.maxlength: raise annotate.InputError("%r is too long, maximum is %d." % (val, self.maxlength)) return r def invalid_email(email): return not re.match(r'^[\w\-~@.+]+\Z', email) or \ re.search(r'@[^@]*@', email) or \ not re.match(r'^[\w\-~.+]+@.*[\w\-~+]+\.[\w]{2,}\Z', email) class Email(MaxlengthString): def coerce(self, val, configurable): r = super(Email, self).coerce(val, configurable) if invalid_email(val): raise annotate.InputError("%r is not a valid email address." % val) return r class MaxlengthStringRenderer(webform.StringRenderer): def input(self, context, slot, data, name, value): if data.typedValue.getAttribute('hidden'): T='hidden' else: T='text' return slot[ tags.input(id=formutils.keyToXMLID(context.key), type=T, name=name, value=value, maxlength=data.typedValue.maxlength, _class="freeform-input-%s" % T)] class ProcessTypedContext(compy.Adapter): __implements__ = iformless.IInputProcessor, def process(self, context, boundTo, data): """data is a list of strings at this point """ typed = self.original val = data[0] if typed.unicode: try: val = val.decode(getPOSTCharset(context), 'replace') except LookupError: val = val.decode('utf-8', 'replace') if typed.strip: val = val.strip() if val == '' or val is None: if typed.required: raise annotate.InputError(typed.requiredFailMessage) else: return typed.null return typed.coerce(val, boundTo, context) class MaxlengthPassword(MaxlengthString): requiredFailMessage = 'Please enter a password.' class MaxlengthPasswordRenderer(webform.PasswordRenderer): def input(self, context, slot, data, name, value): return [ tags.input(id=formutils.keyToXMLID(context.key), name=name, type="password", _class="freeform-input-password", maxlength=data.typedValue.maxlength), " Again ", tags.input(name="%s____2" % name, type="password", _class="freeform-input-password", maxlength=data.typedValue.maxlength), ] class Decimal(annotate.Typed): requiredFailMessage = 'Please enter a decimal number.' def coerce(self, val, configurable): # TODO: This shouldn't be required; check. # val should never be None, but always a string. if val is None: return None try: return decimal.Decimal(val) except decimal.InvalidOperation: raise annotate.InputError("%r is not a decimal number." % val) class ForcedBoolean(annotate.Boolean): pass class ForcedBooleanRenderer(webform.BooleanRenderer): def input(self, context, slot, data, name, value): ## The only difference here is the "checked" attribute; the value is still the same because ## we want true to be passed to the server when the checkbox is checked and the form ## is posted. node = tags.input(id=formutils.keyToXMLID(context.key), type="checkbox", name=name, value='True', _class="freeform-input-checkbox") if value: node(checked="checked") # HTML forms are so weak. If the checkbox is not checked, no value at all will be # in request.args with the name data.name. So let's force the value False to always # be in request.args[data.name]. If the checkbox is checked, the value True will # be first, and we will find that. return slot[node, tags.input(type="hidden", name=name, value="")] class RequiredChoice(annotate.Choice): requiredFailMessage = 'Please choose an option.' def coerce(self, val, configurable): r = super(RequiredChoice, self).coerce(val, configurable) if self.required and r == self.default: raise annotate.InputError(self.requiredFailMessage) try: x = int(val) except ValueError: x = val if x not in self.choices: raise annotate.InputError("Your web browser is malfunctioning") return r compy.registerAdapter(MaxlengthStringRenderer, MaxlengthString, iformless.ITypedRenderer) compy.registerAdapter(MaxlengthStringRenderer, Email, iformless.ITypedRenderer) compy.registerAdapter(MaxlengthPasswordRenderer, MaxlengthPassword, iformless.ITypedRenderer) compy.registerAdapter(ReadonlyTextRenderer, ReadonlyText, iformless.ITypedRenderer) compy.registerAdapter(webform.ChoiceRenderer, RequiredChoice, iformless.ITypedRenderer) compy.registerAdapter(ForcedBooleanRenderer, ForcedBoolean, iformless.ITypedRenderer) compy.registerAdapter(processors.ProcessPassword, MaxlengthPassword, iformless.IInputProcessor) webform.freeformDefaultContentPattern = tags.invisible[ tags.label(_class="freeform-label", _for=stan.slot('id'))[ stan.slot('label') ], tags.div(_class="freeform-input")[ stan.slot('input') ], tags.div(_class="freeform-error")[ stan.slot('error') ], tags.div(_class="freeform-description")[tags.label(_for=stan.slot('id'))[ stan.slot('description') ]]].freeze()

On Mon, Jan 31, 2005 at 06:35:00PM +0100, andrea@cpushare.com wrote:
class Email(MaxlengthString): def coerce(self, val, configurable): r = super(Email, self).coerce(val, configurable) if invalid_email(val): raise annotate.InputError("%r is not a valid email address." % val) return r
Just in case somebody uses the above, I noticed one little bug with the case, see now I return r.lower(). def invalid_email(email): return not re.match(r'^[\w\-~@.+]+\Z', email) or \ re.search(r'@[^@]*@', email) or \ not re.match(r'^[\w\-~.+]+@.*[\w\-~+]+\.[\w]{2,}\Z', email) class Email(MaxlengthString): def coerce(self, val, configurable): r = super(Email, self).coerce(val, configurable) if invalid_email(r): raise annotate.InputError("%r is not a valid email address." % val) return r.lower() Now I'm doing checking on the domain with twisted dns capabilities too before trying to send an email address (this helps against typos). But I do that in the handlers, not in the coerce method (it apparently doesn't like if I return a deferred from a coerce method). No problem of course (though it'd be a bit cleaner if I could handle deferreds in the coerce methods too). I'd need ctx passed to coerce too, I had to implement my ProcessTypedContext in one case due the lack of ctx being passed to coerce, as described in the previous email. thanks!
participants (2)
-
Andrea Arcangeli
-
andrea@cpushare.com