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

Last change on this file since 8684 was 8169, checked in by uli, 13 years ago

Shorten code.

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