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

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

Add 'active' property attribute. Use this attribute to show only active (valid) products to anonymous users.

  • Property svn:keywords set to Id
File size: 8.2 KB
Line 
1## $Id: vocabularies.py 12592 2015-02-11 11:59:34Z 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 SimpleIkobaVocabulary, SUBMITTED, VERIFIED
32from waeup.ikoba.interfaces import MessageFactory as _
33from waeup.ikoba.utils.helpers import get_sorted_preferred
34from waeup.ikoba.utils.countries import COUNTRIES
35from waeup.ikoba.payments.currencies import ISO_4217_CURRENCIES
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
62
63class RegNumNotInSource(ValidationError):
64    """Registration number in use.
65    """
66    # The docstring of ValidationErrors is used as error description
67    # by zope.formlib.
68    pass
69
70class EmailNotInSource(ValidationError):
71    """Email address in use.
72    """
73    pass
74
75
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'
92
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
121
122def contextual_reg_num_source(context):
123    source = RegNumberSource(context)
124    return source
125
126directlyProvides(contextual_reg_num_source, IContextSourceBinder)
127
128class EmailSource(RegNumberSource):
129    field_name = 'email'
130    validation_error = EmailNotInSource
131
132def contextual_email_source(context):
133    source = EmailSource(context)
134    return source
135directlyProvides(contextual_email_source, IContextSourceBinder)
136
137
138class ConCatProductSource(BasicContextualSourceFactory):
139    """A contract category product source delivers all products
140    which belong to a certain contract_category.
141    """
142
143    def getValues(self, context):
144        concat = getattr(context, 'contract_category', None)
145        products = grok.getSite()['products'].values()
146        if not concat:
147            return products
148        resultlist = [
149            value for value in products if value.contract_category == concat]
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)
157
158
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
169       
170        resultlist = [
171            value for value in products
172            if value.contract_category == concat and value.active
173            ]
174        return resultlist
175
176
177class CustomerDocumentSource(BasicContextualSourceFactory):
178    """A customer document source delivers all submitted and verified documents
179    of the context customer.
180    """
181
182    def getValues(self, context):
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))
188        resultlist = [
189            value for value in results if value.state in (SUBMITTED, VERIFIED)]
190        return resultlist
191
192    def getToken(self, context, value):
193        return value.document_id
194
195    def getTitle(self, context, value):
196        return "%s... - %s" % (value.document_id[:9], value.title)
197
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):
215    """A product option source delivers all options belonging to
216    a selected product.
217    """
218
219    source_class = ProductOptionSource
220
221    def getValues(self, context):
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
233
234    def getToken(self, context, value):
235        return value.title
236
237    def getTitle(self, context, value):
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.