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

Last change on this file since 7857 was 7856, checked in by uli, 13 years ago

More cleanups.

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