"""A phone number widget. This widget is an input widget (not made for display forms but for edit and add forms). It can be used for :class:`zope.schema.TextLine` fields but has to be requested by the form manually (otherwise the regular TextLine widget will be used for rendering). The code was mainly taken from usphone implementation in z3c.widget (which was designed for outdated zope.app.form (instead of zope.formlib or newer form libs). Therefore some modifications were neccessary and it might look a bit overloaded. If you use the PhoneWidget for rendering regular TextLine attributes (preferably in edit forms or add forms), the phone number is displayed by three input fields representing the international code, the area code and the extension line. All three fields require pure numbers as input and do not accept other chars. When the entered input is stored with a context object, it is stored as a single unicode string with the numbers divided by single hyphen. So, input <12>, <111>, <444> becomes the string ``'12-111-444'`` for the context object. Large parts of this module are copied from z3c.widget and may be covered by ZPL. """ import grok import re import copy from waeup.kofa.interfaces import MessageFactory as _ from zope import schema from zope.browserpage import ViewPageTemplateFile from zope.component import getMultiAdapter from zope.formlib import form from zope.formlib.interfaces import ( IBrowserWidget, IWidgetInputErrorView, IInputWidget, WidgetInputError, MissingInputError) from zope.formlib.widget import SimpleInputWidget from zope.interface import Interface, implements from zope.schema.interfaces import RequiredMissing from zc.sourcefactory.contextual import BasicContextualSourceFactory from zope.formlib.textwidgets import ( TextWidget, renderElement, ConversionError) from zope.formlib.widget import ValidationError from zope.component import getUtility from zope.interface import Invalid from zope.formlib.interfaces import ( WidgetInputError, MissingInputError, InputErrors,) from zope.schema.interfaces import RequiredMissing from waeup.kofa.interfaces import MessageFactory as _ class IInternationalPhonePrefixes(Interface): """A dict of international phone number prefixes. """ INT_PHONE_PREFIXES = { _('Germany'): '49', _('Nigeria'): '234', _('U.S.'): '1', } class PhonePrefixes(grok.GlobalUtility): grok.implements(IInternationalPhonePrefixes) _data = INT_PHONE_PREFIXES.items() def title_value_list(self): return sorted([('%s (+%s)' % (x,y), '+%s' % y) for x,y in self._data]) RE_INT_PREFIX = re.compile('^\+\d+') RE_NUMBERS = re.compile('^\d+$') RE_NUMBERS_AND_HYPHENS = re.compile('^[\d\-]+$') class PhoneWidget(TextWidget): subwidget_names = ('country', 'area', 'ext') def _renderPrefixWidget(self, value): prefixes = getUtility( IInternationalPhonePrefixes).title_value_list() options = [] for ptitle, pval in prefixes: selected = '' if value == pval: selected = ' selected="selected" ' options.append( '' % (pval, selected, ptitle)) options = '\n'.join(options) return '' % ( '%s.%s' % (self.name, 'country'), '%s.%s' % (self.name, 'country'), options) def __call__(self): value = self._getFormValue() if value is None or value == self.context.missing_value: value = '' if len(value.split('-')) < 2: value = '--' + value subvalues = value.split('-', 2) kwargs = {'type': self.type, 'name': self.name, 'id': self.name, 'value': value, 'cssClass': self.cssClass, 'style': self.style, 'size': self.displayWidth, 'extra': self.extra} if self.displayMaxWidth: kwargs['maxlength'] = self.displayMaxWidth # TODO This is untested. fields = [] for num, subname in enumerate(self.subwidget_names): if num == 0: select = self._renderPrefixWidget(subvalues[num]) fields.append(select) continue print select kwargs.update(name = '%s.%s' % (self.name, subname)) kwargs.update(id=kwargs['name']) # kwargs.update(cssClass = '%s %s' % (self.cssClass, 'span1')) kwargs.update(cssClass = '%s %s' % ('', 'span2')) kwargs.update(value = subvalues[num]) fields.append(renderElement(self.tag, **kwargs)) return '-'.join(fields) def _getFormInput(self): """Returns current form input. The value returned must be in a format that can be used as the 'input' argument to `_toFieldValue`. The default implementation returns the form value that corresponds to the widget's name. Subclasses may override this method if their form input consists of more than one form element or use an alternative naming convention. """ result = '-'.join( [self.request.get('%s.%s' % (self.name, name), '') for name in self.subwidget_names]) return result def _toFieldValue(self, input): """Check value entered in form further. Raises ConversionError if values entered contain non-numbers. For the extension line we silently allow slashes as well. """ result = super(PhoneWidget, self)._toFieldValue(input) parts = input.split('-', 2) if '' in parts and self.context.required: raise ConversionError( _("Empty phone field(s)."), MissingInputError( self.name, self.label, None)) if parts[0] != '' and not RE_INT_PREFIX.match(parts[0]): raise ConversionError( _("Int. prefix requires format '+NNN'"), ValueError('invalid international prefix')) if (parts[1] != '' and not RE_NUMBERS.match(parts[1])) or ( parts[2] != '' and not RE_NUMBERS_AND_HYPHENS.match( parts[2])): raise ConversionError( _("Phone numbers may contain numbers only."), ValueError('non numbers in phone number')) if result in ('--', '', None): result = self.context.missing_value return result def _getFormValue(self): """Returns a value suitable for use in an HTML form. Detects the status of the widget and selects either the input value that came from the request, the value from the _data attribute or the default value. """ try: input_value = self._getCurrentValueHelper() except InputErrors: form_value = '-'.join( [self.request.form.get('%s.%s' % (self.name, name), '') for name in self.subwidget_names] ) else: form_value = self._toFormValue(input_value) return form_value def hasInput(self): """A phone widget has input if all three subfields have input. """ for name in self.subwidget_names: if '%s.%s' % (self.name, name) not in self.request.form: return False return True