source: main/waeup.ikoba/trunk/src/waeup/ikoba/customers/vocabularies.py @ 13066

Last change on this file since 13066 was 12839, checked in by Henrik Bettermann, 10 years ago

Fix docstrings as slanged by Sphinx.

  • Property svn:keywords set to Id
File size: 9.3 KB
Line 
1## $Id: vocabularies.py 12839 2015-03-31 17:55:45Z henrik $
2##
3## Copyright (C) 2014 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"""Vocabularies and sources for the customer section.
19"""
20import grok
21from zope.component import getUtility, queryUtility
22from zope.catalog.interfaces import ICatalog
23from zope.interface import implements, directlyProvides
24from zope.schema.interfaces import (
25    ISource, IContextSourceBinder, ValidationError)
26from zc.sourcefactory.basic import BasicSourceFactory
27from zc.sourcefactory.contextual import BasicContextualSourceFactory
28from zc.sourcefactory.source import FactoredContextualSource
29from zc.sourcefactory.interfaces import IContextualSource
30from waeup.ikoba.sourcefactory import SmartBasicContextualSourceFactory
31from waeup.ikoba.interfaces import (
32    SimpleIkobaVocabulary, SUBMITTED, VERIFIED, APPROVED
33    )
34from waeup.ikoba.interfaces import MessageFactory as _
35from waeup.ikoba.utils.helpers import get_sorted_preferred
36from waeup.ikoba.utils.countries import COUNTRIES
37from waeup.ikoba.payments.currencies import ISO_4217_CURRENCIES
38
39#: a tuple of tuples (<COUNTRY-NAME>, <ISO-CODE>) with Nigeria first.
40COUNTRIES = get_sorted_preferred(COUNTRIES, ['NG'])
41nats_vocab = SimpleIkobaVocabulary(*COUNTRIES)
42
43
44class GenderSource(BasicSourceFactory):
45    """A gender source delivers basically a mapping
46       ``{'m': 'Male', 'f': 'Female'}``
47
48       Using a source, we make sure that the tokens (which are
49       stored/expected for instance from CSV files) are something one
50       can expect and not cryptic IntIDs.
51    """
52    def getValues(self):
53        return ['m', 'f']
54
55    def getToken(self, value):
56        return value[0].lower()
57
58    def getTitle(self, value):
59        if value == 'm':
60            return _('male')
61        if value == 'f':
62            return _('female')
63
64
65class RegNumNotInSource(ValidationError):
66    """Registration number in use.
67    """
68    # The docstring of ValidationErrors is used as error description
69    # by zope.formlib.
70    pass
71
72class EmailNotInSource(ValidationError):
73    """Email address in use.
74    """
75    pass
76
77
78class RegNumberSource(object):
79    """A source that accepts any entry for a certain field if not used
80    already.
81
82    Using this kind of source means a way of setting an invariant.
83
84    We accept a value iff:
85    - the value cannot be found in catalog or
86    - the value can be found as part of some item but the bound item
87    is the context object itself.
88    """
89    implements(ISource)
90    cat_name = 'customers_catalog'
91    field_name = 'reg_number'
92    validation_error = RegNumNotInSource
93    comp_field = 'customer_id'
94
95    def __init__(self, context):
96        self.context = context
97        return
98
99    def __contains__(self, value):
100        """We accept all values not already given to other customers.
101        """
102        cat = queryUtility(ICatalog, self.cat_name)
103        if cat is None:
104            return True
105        kw = {self.field_name: (value, value)}
106        results = cat.searchResults(**kw)
107        for entry in results:
108            if not hasattr(self.context, self.comp_field):
109                # we have no context with comp_field (most probably
110                # while adding a new object, where the container is
111                # the context) which means that the value was given
112                # already to another object (as _something_ was found in
113                # the catalog with that value). Fail on first round.
114                raise self.validation_error(value)
115            if getattr(entry, self.comp_field) != getattr(
116                self.context, self.comp_field):
117                # An entry already given to another customer is not in our
118                # range of acceptable values.
119                raise self.validation_error(value)
120                #return False
121        return True
122
123
124def contextual_reg_num_source(context):
125    source = RegNumberSource(context)
126    return source
127
128directlyProvides(contextual_reg_num_source, IContextSourceBinder)
129
130class EmailSource(RegNumberSource):
131    field_name = 'email'
132    validation_error = EmailNotInSource
133
134def contextual_email_source(context):
135    source = EmailSource(context)
136    return source
137directlyProvides(contextual_email_source, IContextSourceBinder)
138
139
140class ConCatProductSource(BasicContextualSourceFactory):
141    """A contract category product source delivers all products
142    which belong to a certain contract_category.
143    """
144
145    def getValues(self, context):
146        concat = getattr(context, 'contract_category', None)
147        products = grok.getSite()['products'].values()
148        if not concat:
149            return products
150        resultlist = [
151            value for value in products if value.contract_category == concat]
152        return resultlist
153
154    def getToken(self, context, value):
155        return value.product_id
156
157    def getTitle(self, context, value):
158        return "%s - %s" % (value.product_id, value.title)
159
160
161class ConCatActiveProductSource(ConCatProductSource):
162    """A contract category product source delivers all products
163    which belong to a certain contract_category.
164    """
165
166    def getValues(self, context):
167        concat = getattr(context, 'contract_category', None)
168        products = grok.getSite()['products'].values()
169        if not concat:
170            return products
171        resultlist = [
172            value for value in products
173            if value.contract_category == concat and value.active
174            ]
175        return resultlist
176
177
178class CustomerDocumentSource(BasicContextualSourceFactory):
179    """A customer document source delivers all submitted and verified documents
180    of the context customer.
181    """
182
183    def getValues(self, context):
184        # When checking conversion during import, contracts do not belong to
185        # customers. Thus all portal documents must be returned.
186        user_id = getattr(getattr(context, 'customer', None), 'user_id', None)
187        catalog = getUtility(ICatalog, name='documents_catalog')
188        results = catalog.searchResults(user_id=(user_id, user_id))
189        resultlist = [
190            value for value in results if value.state in (SUBMITTED, VERIFIED)]
191        return resultlist
192
193    def getToken(self, context, value):
194        return value.document_id
195
196    def getTitle(self, context, value):
197        return "%s - %s" % (value.document_id[:9], value.title)
198
199
200class IProductOptionSource(IContextualSource):
201    """A source operating in context.
202
203    This is a marker interface needed for the registration of the
204    IkobaSequenceWidget when using the List-Choice-ProductOptionSourceFactory
205    field combination primarily in IContract as a replacement for
206    the SourceOrderedMultiSelectWidget.
207    """
208
209
210class ProductOptionSource(FactoredContextualSource):
211
212      implements(IProductOptionSource)
213
214
215class ProductOptionSourceFactory(BasicContextualSourceFactory):
216    """A product option source delivers all options belonging to
217    a selected product.
218    """
219
220    source_class = ProductOptionSource
221
222    def getValues(self, context):
223        options = []
224        recent_options = getattr(
225            getattr(context, 'product_object', None), 'options', [])
226        # Check and add stored options first before adding recent
227        # product options.
228        if context.product_options:
229            for stored_option in context.product_options:
230                if stored_option not in recent_options:
231                    options.append(stored_option)
232        options += recent_options
233        return options
234
235    def getToken(self, context, value):
236        return "%s_%s_%s" % (value.title, value.fee, value.currency)
237
238    def getTitle(self, context, value):
239        currency = ISO_4217_CURRENCIES[value.currency][1]
240        return "%s @ %s %s" % (value.title, value.fee, currency)
241
242
243class IRefereeSource(IContextualSource):
244    """A source operating in context.
245
246    This is a marker interface needed for the registration of the
247    IkobaSequenceWidget when using the List-Choice-RefereeSourceFactory
248    field combination primarily in ICustomer as a replacement for
249    the SourceOrderedMultiSelectWidget.
250    """
251
252
253class RefereeSource(FactoredContextualSource):
254
255      implements(IRefereeSource)
256
257
258class RefereeSourceFactory(BasicContextualSourceFactory):
259    """A referee source delivers all registered and approved customers.
260    """
261
262    source_class = RefereeSource
263
264    def getValues(self, context):
265        user_id = getattr(getattr(context, 'customer', None), 'user_id', None)
266        catalog = getUtility(ICatalog, name='customers_catalog')
267        results = catalog.searchResults(state=(APPROVED, APPROVED))
268        return list(results)
269
270    def getToken(self, context, value):
271        return value.customer_id
272
273    def getTitle(self, context, value):
274        return "%s (%s)" % (value.display_fullname, value.customer_id[:9])
Note: See TracBrowser for help on using the repository browser.