"""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 re import copy from waeup.sirp 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 class IPhoneData(Interface): """A schema used to generate a Phone widget.""" country = schema.TextLine( title=_('Country Code'), description=_('The country code of the phone number.'), min_length=1, constraint=re.compile(r'^[0-9]+$').search, default=u'234', # int. code for nigeria required=True) area = schema.TextLine( title=_('Area Code'), description=_('The area code of the phone number.'), min_length=1, constraint=re.compile(r'^[0-9]+$').search, required=True) extension = schema.TextLine( title=_('Direct Line'), description=_('The direct line of the phone number.'), min_length=3, constraint=re.compile(r'^[0-9]{3,}$').search, required=True) class PhoneWidgetData(object): """Phone number data""" implements(IPhoneData) country = None area = None extension = None def __init__(self, context, number=None): """Set countrycode, areacode, and extension line from number. """ self.context = context if number is None: return # handle other types than strings. if not isinstance(number, basestring): number = str(number) parts = number.split('-', 2) if len(parts) == 2: parts = [None,] + parts elif len(parts) == 0: return elif len(parts) == 1: parts = [None, None] + parts self.country, self.area, self.extension = parts return @property def number(self): """Return a valid phone number string of format ``--``. where is the country code (digits only), is the area code and is the extension line. """ return u'-'.join( (self.country or '', self.area or '', self.extension)) class PhoneWidget(SimpleInputWidget): """Phone Number Widget""" implements(IBrowserWidget, IInputWidget) template = ViewPageTemplateFile('phonewidget.pt') _prefix = 'field.' _error = None widgets = {} # See zope.formlib.interfaces.IWidget name = None visible = True def __init__(self, field, request): super(PhoneWidget, self).__init__(field, request) value = field.query(field.context) self._generateSubWidgets() return def _generateSubWidgets(self): """Create the three subwidgets. """ value = self.context.query(self.context.context) # We have to modify the adapter according to our own # 'required' state. Some ugly workaround adapter = copy.deepcopy(IPhoneData) for name in 'country', 'area', 'extension': adapter[name].required = self.required adapters = {adapter: PhoneWidgetData(self, value)} self.widgets = form.setUpEditWidgets( form.FormFields(IPhoneData), self.name, value, self.request, adapters=adapters) # The displayWidth cant'be set # because it is overridden by Bootstrap css classes #self.widgets['country'].displayWidth = 3 #self.widgets['area'].displayWidth = 5 #self.widgets['extension'].displayWidth = 10 # We have to set the cssClass attributes self.widgets['country'].cssClass = 'span2' self.widgets['area'].cssClass = 'span2' self.widgets['extension'].cssClass = 'span3' return def setRenderedValue(self, value): """See zope.formlib.interfaces.IWidget""" if isinstance(value, unicode) and '-' in value: country, area, extension = value.split('-', 2) self.widgets['country'].setRenderedValue(country) self.widgets['area'].setRenderedValue(area) self.widgets['extension'].setRenderedValue(extension) return return def setPrefix(self, prefix): """See zope.formlib.interfaces.IWidget""" # Set the prefix locally if not prefix.endswith("."): prefix += '.' self._prefix = prefix self.name = prefix + self.context.__name__ # Now distribute it to the sub-widgets self._generateSubWidgets() return def getInputValue(self): """See zope.formlib.interfaces.IInputWidget""" self._error = None try: result = u'-'.join(( self.widgets['country'].getInputValue(), self.widgets['area'].getInputValue(), self.widgets['extension'].getInputValue() )) return result except TypeError: if self.required: return self._missing return None except ValueError, v: self._error = WidgetInputError( self.context.__name__, self.label, _(v)) raise self._error except WidgetInputError, e: self._error = e raise e def applyChanges(self, content): """See zope.formlib.interfaces.IInputWidget""" field = self.context new_value = self.getInputValue() old_value = field.query(content, self) # The selection has not changed if new_value == old_value: return False field.set(content, new_value) return True def hasInput(self): """See zope.formlib.interfaces.IInputWidget""" return (self.widgets['country'].hasInput() and (self.widgets['area'].hasInput() and self.widgets['extension'].hasInput())) def hasValidInput(self): """See zope.formlib.interfaces.IInputWidget""" return (self.widgets['country'].hasValidInput() and self.widgets['area'].hasValidInput() and self.widgets['extension'].hasValidInput()) def hidden(self): """See zope.formlib.interfaces.IBrowserWidget""" output = [] output.append(self.widgets['country'].hidden()) output.append(self.widgets['area'].hidden()) output.append(self.widgets['extension'].hidden()) return '\n'.join(output) def error(self): """See zope.formlib.interfaces.IBrowserWidget""" if self._error: return getMultiAdapter( (self._error, self.request), IWidgetInputErrorView).snippet() country_error = self.widgets['country'].error() if country_error: return country_error area_error = self.widgets['area'].error() if area_error: return area_error extension_error = self.widgets['extension'].error() if extension_error: return extension_error return "" def __call__(self): """See zope.formlib.interfaces.IBrowserWidget""" return self.template()