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

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

Make the phonewidget handling non-standardized input more gracefully.

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