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

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

We need to produce unique tokens in ProductOptionSourceFactory?.

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