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

Last change on this file since 13810 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
RevLine 
[12092]1## $Id: vocabularies.py 12839 2015-03-31 17:55:45Z 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):
[12127]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))
[12100]189        resultlist = [
[12127]190            value for value in results if value.state in (SUBMITTED, VERIFIED)]
[12100]191        return resultlist
192
193    def getToken(self, context, value):
194        return value.document_id
195
196    def getTitle(self, context, value):
[12809]197        return "%s - %s" % (value.document_id[:9], value.title)
[12324]198
[12329]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):
[12324]216    """A product option source delivers all options belonging to
217    a selected product.
218    """
219
[12329]220    source_class = ProductOptionSource
221
[12324]222    def getValues(self, context):
[12340]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
[12324]234
235    def getToken(self, context, value):
[12770]236        return "%s_%s_%s" % (value.title, value.fee, value.currency)
[12324]237
238    def getTitle(self, context, value):
[12343]239        currency = ISO_4217_CURRENCIES[value.currency][1]
240        return "%s @ %s %s" % (value.title, value.fee, currency)
[12785]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.