source: main/waeup.kofa/trunk/src/waeup/kofa/widgets/phonewidget.py @ 7854

Last change on this file since 7854 was 7852, checked in by uli, 13 years ago

Enable new phone widget and remove the old one.

  • Property svn:keywords set to Id
File size: 7.4 KB
RevLine 
[7342]1"""A phone number widget.
2
3This widget is an input widget (not made for display forms but for
4edit and add forms).
5
6It can be used for :class:`zope.schema.TextLine` fields but has to be
7requested by the form manually (otherwise the regular TextLine widget
8will be used for rendering).
9
10The code was mainly taken from usphone implementation in z3c.widget
11(which was designed for outdated zope.app.form (instead of
12zope.formlib or newer form libs). Therefore some modifications were
13neccessary and it might look a bit overloaded.
[7352]14
15If you use the PhoneWidget for rendering regular TextLine attributes
16(preferably in edit forms or add forms), the phone number is displayed
17by three input fields representing the international code, the area
18code and the extension line. All three fields require pure numbers as
19input and do not accept other chars.
20
21When the entered input is stored with a context object, it is stored
22as a single unicode string with the numbers divided by single hyphen.
23
24So, input <12>, <111>, <444> becomes the string ``'12-111-444'`` for
25the context object.
26
[7418]27Large parts of this module are copied from z3c.widget and may be
28covered by ZPL.
29
[7342]30"""
[7840]31import grok
[7342]32import re
[7439]33import copy
[7811]34from waeup.kofa.interfaces import MessageFactory as _
[7342]35from zope import schema
36from zope.browserpage import ViewPageTemplateFile
37from zope.component import getMultiAdapter
38from zope.formlib import form
[7411]39from zope.formlib.interfaces import (
40    IBrowserWidget, IWidgetInputErrorView, IInputWidget, WidgetInputError,
41    MissingInputError)
[7342]42from zope.formlib.widget import SimpleInputWidget
43from zope.interface import Interface, implements
[7411]44from zope.schema.interfaces import RequiredMissing
[7342]45
46
[7840]47from zc.sourcefactory.contextual import BasicContextualSourceFactory
[7846]48from zope.formlib.textwidgets import (
49    TextWidget, renderElement, ConversionError)
50from zope.formlib.widget import ValidationError
[7840]51from zope.component import getUtility
52from zope.interface import Invalid
[7846]53from zope.formlib.interfaces import (
54    WidgetInputError, MissingInputError, InputErrors,)
55from zope.schema.interfaces import RequiredMissing
56from waeup.kofa.interfaces import MessageFactory as _
[7840]57
58class IInternationalPhonePrefixes(Interface):
59    """A dict of international phone number prefixes.
60    """
61
62INT_PHONE_PREFIXES = {
[7846]63        _('Germany'): '49',
64        _('Nigeria'): '234',
65        _('U.S.'): '1',
[7840]66        }
67
68class PhonePrefixes(grok.GlobalUtility):
69    grok.implements(IInternationalPhonePrefixes)
70
71    _data = INT_PHONE_PREFIXES.items()
72
73    def title_value_list(self):
[7848]74        return sorted([('%s (+%s)' % (x,y), '+%s' % y)
[7840]75                       for x,y in self._data])
76
[7848]77RE_INT_PREFIX = re.compile('^\+\d+')
[7846]78RE_NUMBERS = re.compile('^\d+$')
79RE_NUMBERS_AND_HYPHENS = re.compile('^[\d\-]+$')
80
[7852]81class PhoneWidget(TextWidget):
[7840]82
83    subwidget_names = ('country', 'area', 'ext')
84
85    def _renderPrefixWidget(self, value):
86        prefixes = getUtility(
87            IInternationalPhonePrefixes).title_value_list()
88        options = []
89        for ptitle, pval in prefixes:
90            selected = ''
91            if value == pval:
92                selected = ' selected="selected" '
93            options.append(
94                '<option value="%s"%s>%s</option>' % (pval, selected, ptitle))
95        options = '\n'.join(options)
96        return '<select id="%s" name="%s" size="1" class="span4">\n%s\n</select>' % (
97            '%s.%s' % (self.name, 'country'),
98            '%s.%s' % (self.name, 'country'),
99            options)
100
101    def __call__(self):
102        value = self._getFormValue()
103        if value is None or value == self.context.missing_value:
104            value = ''
105        if len(value.split('-')) < 2:
106            value = '--' + value
107        subvalues = value.split('-', 2)
108
109        kwargs = {'type': self.type,
110                  'name': self.name,
111                  'id': self.name,
112                  'value': value,
113                  'cssClass': self.cssClass,
114                  'style': self.style,
115                  'size': self.displayWidth,
116                  'extra': self.extra}
117        if self.displayMaxWidth:
118            kwargs['maxlength'] = self.displayMaxWidth # TODO This is untested.
119        fields = []
120        for num, subname in enumerate(self.subwidget_names):
121            if num == 0:
122                select = self._renderPrefixWidget(subvalues[num])
123                fields.append(select)
124                continue
125                print select
126            kwargs.update(name = '%s.%s' % (self.name, subname))
127            kwargs.update(id=kwargs['name'])
128            # kwargs.update(cssClass = '%s %s' % (self.cssClass, 'span1'))
129            kwargs.update(cssClass = '%s %s' % ('', 'span2'))
130            kwargs.update(value = subvalues[num])
131            fields.append(renderElement(self.tag, **kwargs))
132        return '-'.join(fields)
133
134    def _getFormInput(self):
135        """Returns current form input.
136
137        The value returned must be in a format that can be used as the 'input'
138        argument to `_toFieldValue`.
139
140        The default implementation returns the form value that corresponds to
141        the widget's name. Subclasses may override this method if their form
142        input consists of more than one form element or use an alternative
143        naming convention.
144        """
145        result = '-'.join(
[7846]146            [self.request.get('%s.%s' % (self.name, name), '')
[7840]147             for name in self.subwidget_names])
148        return result
149
[7846]150    def _toFieldValue(self, input):
151        """Check value entered in form further.
152
153        Raises ConversionError if values entered contain non-numbers.
154
155        For the extension line we silently allow slashes as well.
156        """
[7852]157        result = super(PhoneWidget, self)._toFieldValue(input)
[7846]158        parts = input.split('-', 2)
159        if '' in parts and self.context.required:
160            raise ConversionError(
161                _("Empty phone field(s)."), MissingInputError(
162                self.name, self.label, None))
[7848]163        if parts[0] != '' and not RE_INT_PREFIX.match(parts[0]):
[7846]164            raise ConversionError(
[7848]165                _("Int. prefix requires format '+NNN'"),
166                ValueError('invalid international prefix'))
167        if (parts[1] != '' and not RE_NUMBERS.match(parts[1])) or (
168            parts[2] != '' and not RE_NUMBERS_AND_HYPHENS.match(
169            parts[2])):
170            raise ConversionError(
[7846]171                _("Phone numbers may contain numbers only."),
172                ValueError('non numbers in phone number'))
[7848]173        if result in ('--', '', None):
174            result = self.context.missing_value
[7846]175        return result
176
177    def _getFormValue(self):
178        """Returns a value suitable for use in an HTML form.
179
180        Detects the status of the widget and selects either the input value
181        that came from the request, the value from the _data attribute or the
182        default value.
183        """
184        try:
185            input_value = self._getCurrentValueHelper()
186        except InputErrors:
187            form_value = '-'.join(
188                [self.request.form.get('%s.%s' % (self.name, name), '')
189                 for name in self.subwidget_names]
190                )
191        else:
192            form_value = self._toFormValue(input_value)
193        return form_value
194
[7840]195    def hasInput(self):
[7846]196        """A phone widget has input if all three subfields have input.
197        """
[7840]198        for name in self.subwidget_names:
199            if '%s.%s' % (self.name, name) not in self.request.form:
200                return False
201        return True
Note: See TracBrowser for help on using the repository browser.