source: main/waeup.sirp/trunk/src/waeup/sirp/widgets/phonewidget.py @ 7418

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

Phonewidget is mainly work of others. Not sure whether we can claim
copyrights on it.

  • Property svn:keywords set to Id
File size: 7.9 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
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.
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
27Large parts of this module are copied from z3c.widget and may be
28covered by ZPL.
29
30"""
31import re
32from waeup.sirp import MessageFactory as _
33from zope import schema
34from zope.browserpage import ViewPageTemplateFile
35from zope.component import getMultiAdapter
36from zope.formlib import form
37from zope.formlib.interfaces import (
38    IBrowserWidget, IWidgetInputErrorView, IInputWidget, WidgetInputError,
39    MissingInputError)
40from zope.formlib.widget import SimpleInputWidget
41from zope.interface import Interface, implements
42from zope.schema.interfaces import RequiredMissing
43
44class IPhoneData(Interface):
45    """A schema used to generate a Phone widget."""
46
47    country = schema.TextLine(
48        title=_('Country Code'),
49        description=_('The country code of the phone number.'),
50        min_length=1,
51        constraint=re.compile(r'^[0-9]+$').search,
52        default=u'234', # int. code for nigeria
53        required=True)
54
55    area = schema.TextLine(
56        title=_('Area Code'),
57        description=_('The area code of the phone number.'),
58        min_length=1,
59        constraint=re.compile(r'^[0-9]+$').search,
60        required=True)
61
62    extension = schema.TextLine(
63        title=_('Direct Line'),
64        description=_('The direct line of the phone number.'),
65        min_length=3,
66        constraint=re.compile(r'^[0-9]{3,}$').search,
67        required=True)
68
69
70class PhoneWidgetData(object):
71    """Phone number data"""
72    implements(IPhoneData)
73
74    country = None
75    area = None
76    extension = None
77
78    def __init__(self, context, number=None):
79        """Set countrycode, areacode, and extension line from number.
80        """
81        self.context = context
82        if number is None:
83            return
84        # handle other types than strings.
85        if not isinstance(number, basestring):
86            number = str(number)
87        parts = number.split('-', 2)
88        if len(parts) == 2:
89            parts = [None,] + parts
90        elif len(parts) == 0:
91            return
92        elif len(parts) == 1:
93            parts = [None, None] + parts
94        self.country, self.area, self.extension = parts
95        return
96
97    @property
98    def number(self):
99        """Return a valid phone number string of format ``<IC>-<AC>-<EL>``.
100
101        where <IC> is the country code (digits only), <AC> is the area
102        code and <EL> is the extension line.
103        """
104        return u'-'.join(
105            (self.country or '', self.area or '', self.extension))
106
107
108class PhoneWidget(SimpleInputWidget):
109    """Phone Number Widget"""
110    implements(IBrowserWidget, IInputWidget)
111
112    template = ViewPageTemplateFile('phonewidget.pt')
113    _prefix = 'field.'
114    _error = None
115    widgets = {}
116
117    # See zope.formlib.interfaces.IWidget
118    name = None
119    visible = True
120
121    def __init__(self, field, request):
122        super(PhoneWidget, self).__init__(field, request)
123        value = field.query(field.context)
124        self._generateSubWidgets()
125        return
126
127    def _generateSubWidgets(self):
128        """Create the three subwidgets.
129        """
130        value = self.context.query(self.context.context)
131        adapters = {}
132        adapters[IPhoneData] = PhoneWidgetData(self, value)
133        self.widgets = form.setUpEditWidgets(
134            form.FormFields(IPhoneData),
135            self.name, value, self.request, adapters=adapters)
136        self.widgets['country'].displayWidth = 3
137        self.widgets['area'].displayWidth = 5
138        self.widgets['extension'].displayWidth = 10
139        return
140
141    def setRenderedValue(self, value):
142        """See zope.formlib.interfaces.IWidget"""
143        if isinstance(value, unicode):
144            country, area, extension = value.split('-')
145            self.widgets['country'].setRenderedValue(country)
146            self.widgets['area'].setRenderedValue(area)
147            self.widgets['extension'].setRenderedValue(extension)
148
149
150    def setPrefix(self, prefix):
151        """See zope.formlib.interfaces.IWidget"""
152        # Set the prefix locally
153        if not prefix.endswith("."):
154            prefix += '.'
155        self._prefix = prefix
156        self.name = prefix + self.context.__name__
157        # Now distribute it to the sub-widgets
158        self._generateSubWidgets()
159        return
160
161    def getInputValue(self):
162        """See zope.formlib.interfaces.IInputWidget"""
163        self._error = None
164        try:
165            return u'-'.join((
166                self.widgets['country'].getInputValue(),
167                self.widgets['area'].getInputValue(),
168                self.widgets['extension'].getInputValue() ))
169        except ValueError, v:
170            self._error = WidgetInputError(
171                self.context.__name__, self.label, _(v))
172            raise self._error
173        except WidgetInputError, e:
174            # Subwidgets require input. We have to work around that if
175            # the parent widget does not require input.
176            missing = isinstance(e.errors, RequiredMissing)
177            missing = missing or isinstance(e, MissingInputError)
178            if not self.required and missing:
179                return self._missing
180            self._error = e
181            raise e
182
183    def applyChanges(self, content):
184        """See zope.formlib.interfaces.IInputWidget"""
185        field = self.context
186        new_value = self.getInputValue()
187        old_value = field.query(content, self)
188        # The selection has not changed
189        if new_value == old_value:
190            return False
191        field.set(content, new_value)
192        return True
193
194    def hasInput(self):
195        """See zope.formlib.interfaces.IInputWidget"""
196        return (self.widgets['country'].hasInput() and
197                (self.widgets['area'].hasInput() and
198                 self.widgets['extension'].hasInput()))
199
200
201    def hasValidInput(self):
202        """See zope.formlib.interfaces.IInputWidget"""
203        return (self.widgets['country'].hasValidInput() and
204            self.widgets['area'].hasValidInput() and
205            self.widgets['extension'].hasValidInput())
206
207
208    def hidden(self):
209        """See zope.formlib.interfaces.IBrowserWidget"""
210        output = []
211        output.append(self.widgets['country'].hidden())
212        output.append(self.widgets['area'].hidden())
213        output.append(self.widgets['extension'].hidden())
214        return '\n'.join(output)
215
216
217    def error(self):
218        """See zope.formlib.interfaces.IBrowserWidget"""
219        if self._error:
220            return getMultiAdapter(
221                (self._error, self.request),
222                IWidgetInputErrorView).snippet()
223        country_error = self.widgets['country'].error()
224        if country_error:
225            return country_error
226        area_error = self.widgets['area'].error()
227        if area_error:
228            return area_error
229        extension_error = self.widgets['extension'].error()
230        if extension_error:
231            return extension_error
232        return ""
233
234    def __call__(self):
235        """See zope.formlib.interfaces.IBrowserWidget"""
236        return self.template()
Note: See TracBrowser for help on using the repository browser.