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

Last change on this file since 17664 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
RevLine 
[12092]1## $Id: vocabularies.py 14202 2016-09-29 06:51:42Z henrik $
[11958]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"""
[12092]20import grok
[11958]21from zope.component import getUtility, queryUtility
22from zope.catalog.interfaces import ICatalog
23from zope.interface import implements, directlyProvides
[12329]24from zope.schema.interfaces import (
25    ISource, IContextSourceBinder, ValidationError)
[11958]26from zc.sourcefactory.basic import BasicSourceFactory
27from zc.sourcefactory.contextual import BasicContextualSourceFactory
[12329]28from zc.sourcefactory.source import FactoredContextualSource
29from zc.sourcefactory.interfaces import IContextualSource
[12127]30from waeup.ikoba.sourcefactory import SmartBasicContextualSourceFactory
[12785]31from waeup.ikoba.interfaces import (
32    SimpleIkobaVocabulary, SUBMITTED, VERIFIED, APPROVED
33    )
[11958]34from waeup.ikoba.interfaces import MessageFactory as _
35from waeup.ikoba.utils.helpers import get_sorted_preferred
36from waeup.ikoba.utils.countries import COUNTRIES
[12343]37from waeup.ikoba.payments.currencies import ISO_4217_CURRENCIES
[11958]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
[11985]64
[11958]65class RegNumNotInSource(ValidationError):
[12524]66    """Registration number in use.
[11958]67    """
68    # The docstring of ValidationErrors is used as error description
69    # by zope.formlib.
70    pass
71
[12521]72class EmailNotInSource(ValidationError):
[12524]73    """Email address in use.
[12521]74    """
75    pass
[11985]76
[12521]77
[11958]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
[12839]87    is the context object itself.
[11958]88    """
89    implements(ISource)
90    cat_name = 'customers_catalog'
91    field_name = 'reg_number'
92    validation_error = RegNumNotInSource
93    comp_field = 'customer_id'
[11985]94
[11958]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
[11985]123
[11958]124def contextual_reg_num_source(context):
125    source = RegNumberSource(context)
126    return source
[11985]127
[11958]128directlyProvides(contextual_reg_num_source, IContextSourceBinder)
[12092]129
[12521]130class EmailSource(RegNumberSource):
131    field_name = 'email'
132    validation_error = EmailNotInSource
[12092]133
[12521]134def contextual_email_source(context):
135    source = EmailSource(context)
136    return source
137directlyProvides(contextual_email_source, IContextSourceBinder)
138
139
[12100]140class ConCatProductSource(BasicContextualSourceFactory):
141    """A contract category product source delivers all products
[12097]142    which belong to a certain contract_category.
[12092]143    """
144
145    def getValues(self, context):
[12098]146        concat = getattr(context, 'contract_category', None)
[12092]147        products = grok.getSite()['products'].values()
[12098]148        if not concat:
[12093]149            return products
[12092]150        resultlist = [
[12098]151            value for value in products if value.contract_category == concat]
[12092]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)
[12100]159
160
[12585]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
[12592]173            if value.contract_category == concat and value.active
[12585]174            ]
175        return resultlist
176
177
[12100]178class CustomerDocumentSource(BasicContextualSourceFactory):
[12101]179    """A customer document source delivers all submitted and verified documents
[12100]180    of the context customer.
181    """
182
183    def getValues(self, context):
[14202]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 []
[12127]193        catalog = getUtility(ICatalog, name='documents_catalog')
194        results = catalog.searchResults(user_id=(user_id, user_id))
[12100]195        resultlist = [
[12127]196            value for value in results if value.state in (SUBMITTED, VERIFIED)]
[12100]197        return resultlist
198
199    def getToken(self, context, value):
200        return value.document_id
201
202    def getTitle(self, context, value):
[12809]203        return "%s - %s" % (value.document_id[:9], value.title)
[12324]204
[12329]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):
[12324]222    """A product option source delivers all options belonging to
223    a selected product.
224    """
225
[12329]226    source_class = ProductOptionSource
227
[12324]228    def getValues(self, context):
[12340]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
[12324]240
241    def getToken(self, context, value):
[12770]242        return "%s_%s_%s" % (value.title, value.fee, value.currency)
[12324]243
244    def getTitle(self, context, value):
[12343]245        currency = ISO_4217_CURRENCIES[value.currency][1]
246        return "%s @ %s %s" % (value.title, value.fee, currency)
[12785]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.