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

Last change on this file since 8074 was 8074, checked in by Henrik Bettermann, 12 years ago

The 'ext' part of the phone widget should be optional.

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