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

Last change on this file since 7870 was 7870, checked in by Henrik Bettermann, 13 years ago

Define sorting order of country codes in select boxes.

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