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

Last change on this file since 17388 was 14202, checked in by Henrik Bettermann, 8 years ago

Fix bug. Customers could also select documents from other customers.

  • Property svn:keywords set to Id
File size: 9.5 KB
Line 
1## $Id: vocabularies.py 14202 2016-09-29 06:51:42Z 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 in create mode,
185        # contracts do not belong to customers.
186        # Thus all portal documents must be returned.
187        # Attention: In create mode documents of other customers
188        # can be assigned. ConstraintNotSatisfied is not raised.
189        user_id = getattr(context, 'user_id', None)
190        # Alternative implementation:
191        #if not user_id:
192        #    return []
193        catalog = getUtility(ICatalog, name='documents_catalog')
194        results = catalog.searchResults(user_id=(user_id, user_id))
195        resultlist = [
196            value for value in results if value.state in (SUBMITTED, VERIFIED)]
197        return resultlist
198
199    def getToken(self, context, value):
200        return value.document_id
201
202    def getTitle(self, context, value):
203        return "%s - %s" % (value.document_id[:9], value.title)
204
205
206class IProductOptionSource(IContextualSource):
207    """A source operating in context.
208
209    This is a marker interface needed for the registration of the
210    IkobaSequenceWidget when using the List-Choice-ProductOptionSourceFactory
211    field combination primarily in IContract as a replacement for
212    the SourceOrderedMultiSelectWidget.
213    """
214
215
216class ProductOptionSource(FactoredContextualSource):
217
218      implements(IProductOptionSource)
219
220
221class ProductOptionSourceFactory(BasicContextualSourceFactory):
222    """A product option source delivers all options belonging to
223    a selected product.
224    """
225
226    source_class = ProductOptionSource
227
228    def getValues(self, context):
229        options = []
230        recent_options = getattr(
231            getattr(context, 'product_object', None), 'options', [])
232        # Check and add stored options first before adding recent
233        # product options.
234        if context.product_options:
235            for stored_option in context.product_options:
236                if stored_option not in recent_options:
237                    options.append(stored_option)
238        options += recent_options
239        return options
240
241    def getToken(self, context, value):
242        return "%s_%s_%s" % (value.title, value.fee, value.currency)
243
244    def getTitle(self, context, value):
245        currency = ISO_4217_CURRENCIES[value.currency][1]
246        return "%s @ %s %s" % (value.title, value.fee, currency)
247
248
249class IRefereeSource(IContextualSource):
250    """A source operating in context.
251
252    This is a marker interface needed for the registration of the
253    IkobaSequenceWidget when using the List-Choice-RefereeSourceFactory
254    field combination primarily in ICustomer as a replacement for
255    the SourceOrderedMultiSelectWidget.
256    """
257
258
259class RefereeSource(FactoredContextualSource):
260
261      implements(IRefereeSource)
262
263
264class RefereeSourceFactory(BasicContextualSourceFactory):
265    """A referee source delivers all registered and approved customers.
266    """
267
268    source_class = RefereeSource
269
270    def getValues(self, context):
271        user_id = getattr(getattr(context, 'customer', None), 'user_id', None)
272        catalog = getUtility(ICatalog, name='customers_catalog')
273        results = catalog.searchResults(state=(APPROVED, APPROVED))
274        return list(results)
275
276    def getToken(self, context, value):
277        return value.customer_id
278
279    def getTitle(self, context, value):
280        return "%s (%s)" % (value.display_fullname, value.customer_id[:9])
Note: See TracBrowser for help on using the repository browser.