"""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