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

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

Harmonize copyright message.

  • Property svn:keywords set to Id
File size: 8.6 KB
Line 
1## $Id: phonewidget.py 7416 2011-12-21 07:18:38Z henrik $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18"""A phone number widget.
19
20This widget is an input widget (not made for display forms but for
21edit and add forms).
22
23It can be used for :class:`zope.schema.TextLine` fields but has to be
24requested by the form manually (otherwise the regular TextLine widget
25will be used for rendering).
26
27The code was mainly taken from usphone implementation in z3c.widget
28(which was designed for outdated zope.app.form (instead of
29zope.formlib or newer form libs). Therefore some modifications were
30neccessary and it might look a bit overloaded.
31
32If you use the PhoneWidget for rendering regular TextLine attributes
33(preferably in edit forms or add forms), the phone number is displayed
34by three input fields representing the international code, the area
35code and the extension line. All three fields require pure numbers as
36input and do not accept other chars.
37
38When the entered input is stored with a context object, it is stored
39as a single unicode string with the numbers divided by single hyphen.
40
41So, input <12>, <111>, <444> becomes the string ``'12-111-444'`` for
42the context object.
43
44"""
45import re
46from waeup.sirp import MessageFactory as _
47from zope import schema
48from zope.browserpage import ViewPageTemplateFile
49from zope.component import getMultiAdapter
50from zope.formlib import form
51from zope.formlib.interfaces import (
52    IBrowserWidget, IWidgetInputErrorView, IInputWidget, WidgetInputError,
53    MissingInputError)
54from zope.formlib.widget import SimpleInputWidget
55from zope.interface import Interface, implements
56from zope.schema.interfaces import RequiredMissing
57
58class IPhoneData(Interface):
59    """A schema used to generate a Phone widget."""
60
61    country = schema.TextLine(
62        title=_('Country Code'),
63        description=_('The country code of the phone number.'),
64        min_length=1,
65        constraint=re.compile(r'^[0-9]+$').search,
66        default=u'234', # int. code for nigeria
67        required=True)
68
69    area = schema.TextLine(
70        title=_('Area Code'),
71        description=_('The area code of the phone number.'),
72        min_length=1,
73        constraint=re.compile(r'^[0-9]+$').search,
74        required=True)
75
76    extension = schema.TextLine(
77        title=_('Direct Line'),
78        description=_('The direct line of the phone number.'),
79        min_length=3,
80        constraint=re.compile(r'^[0-9]{3,}$').search,
81        required=True)
82
83
84class PhoneWidgetData(object):
85    """Phone number data"""
86    implements(IPhoneData)
87
88    country = None
89    area = None
90    extension = None
91
92    def __init__(self, context, number=None):
93        """Set countrycode, areacode, and extension line from number.
94        """
95        self.context = context
96        if number is None:
97            return
98        # handle other types than strings.
99        if not isinstance(number, basestring):
100            number = str(number)
101        parts = number.split('-', 2)
102        if len(parts) == 2:
103            parts = [None,] + parts
104        elif len(parts) == 0:
105            return
106        elif len(parts) == 1:
107            parts = [None, None] + parts
108        self.country, self.area, self.extension = parts
109        return
110
111    @property
112    def number(self):
113        """Return a valid phone number string of format ``<IC>-<AC>-<EL>``.
114
115        where <IC> is the country code (digits only), <AC> is the area
116        code and <EL> is the extension line.
117        """
118        return u'-'.join(
119            (self.country or '', self.area or '', self.extension))
120
121
122class PhoneWidget(SimpleInputWidget):
123    """Phone Number Widget"""
124    implements(IBrowserWidget, IInputWidget)
125
126    template = ViewPageTemplateFile('phonewidget.pt')
127    _prefix = 'field.'
128    _error = None
129    widgets = {}
130
131    # See zope.formlib.interfaces.IWidget
132    name = None
133    visible = True
134
135    def __init__(self, field, request):
136        super(PhoneWidget, self).__init__(field, request)
137        value = field.query(field.context)
138        self._generateSubWidgets()
139        return
140
141    def _generateSubWidgets(self):
142        """Create the three subwidgets.
143        """
144        value = self.context.query(self.context.context)
145        adapters = {}
146        adapters[IPhoneData] = PhoneWidgetData(self, value)
147        self.widgets = form.setUpEditWidgets(
148            form.FormFields(IPhoneData),
149            self.name, value, self.request, adapters=adapters)
150        self.widgets['country'].displayWidth = 3
151        self.widgets['area'].displayWidth = 5
152        self.widgets['extension'].displayWidth = 10
153        return
154
155    def setRenderedValue(self, value):
156        """See zope.formlib.interfaces.IWidget"""
157        if isinstance(value, unicode):
158            country, area, extension = value.split('-')
159            self.widgets['country'].setRenderedValue(country)
160            self.widgets['area'].setRenderedValue(area)
161            self.widgets['extension'].setRenderedValue(extension)
162
163
164    def setPrefix(self, prefix):
165        """See zope.formlib.interfaces.IWidget"""
166        # Set the prefix locally
167        if not prefix.endswith("."):
168            prefix += '.'
169        self._prefix = prefix
170        self.name = prefix + self.context.__name__
171        # Now distribute it to the sub-widgets
172        self._generateSubWidgets()
173        return
174
175    def getInputValue(self):
176        """See zope.formlib.interfaces.IInputWidget"""
177        self._error = None
178        try:
179            return u'-'.join((
180                self.widgets['country'].getInputValue(),
181                self.widgets['area'].getInputValue(),
182                self.widgets['extension'].getInputValue() ))
183        except ValueError, v:
184            self._error = WidgetInputError(
185                self.context.__name__, self.label, _(v))
186            raise self._error
187        except WidgetInputError, e:
188            # Subwidgets require input. We have to work around that if
189            # the parent widget does not require input.
190            missing = isinstance(e.errors, RequiredMissing)
191            missing = missing or isinstance(e, MissingInputError)
192            if not self.required and missing:
193                return self._missing
194            self._error = e
195            raise e
196
197    def applyChanges(self, content):
198        """See zope.formlib.interfaces.IInputWidget"""
199        field = self.context
200        new_value = self.getInputValue()
201        old_value = field.query(content, self)
202        # The selection has not changed
203        if new_value == old_value:
204            return False
205        field.set(content, new_value)
206        return True
207
208    def hasInput(self):
209        """See zope.formlib.interfaces.IInputWidget"""
210        return (self.widgets['country'].hasInput() and
211                (self.widgets['area'].hasInput() and
212                 self.widgets['extension'].hasInput()))
213
214
215    def hasValidInput(self):
216        """See zope.formlib.interfaces.IInputWidget"""
217        return (self.widgets['country'].hasValidInput() and
218            self.widgets['area'].hasValidInput() and
219            self.widgets['extension'].hasValidInput())
220
221
222    def hidden(self):
223        """See zope.formlib.interfaces.IBrowserWidget"""
224        output = []
225        output.append(self.widgets['country'].hidden())
226        output.append(self.widgets['area'].hidden())
227        output.append(self.widgets['extension'].hidden())
228        return '\n'.join(output)
229
230
231    def error(self):
232        """See zope.formlib.interfaces.IBrowserWidget"""
233        if self._error:
234            return getMultiAdapter(
235                (self._error, self.request),
236                IWidgetInputErrorView).snippet()
237        country_error = self.widgets['country'].error()
238        if country_error:
239            return country_error
240        area_error = self.widgets['area'].error()
241        if area_error:
242            return area_error
243        extension_error = self.widgets['extension'].error()
244        if extension_error:
245            return extension_error
246        return ""
247
248    def __call__(self):
249        """See zope.formlib.interfaces.IBrowserWidget"""
250        return self.template()
Note: See TracBrowser for help on using the repository browser.