## $Id: vocabularies.py 14202 2016-09-29 06:51:42Z henrik $ ## ## Copyright (C) 2014 Uli Fouquet & Henrik Bettermann ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## """Vocabularies and sources for the customer section. """ import grok from zope.component import getUtility, queryUtility from zope.catalog.interfaces import ICatalog from zope.interface import implements, directlyProvides from zope.schema.interfaces import ( ISource, IContextSourceBinder, ValidationError) from zc.sourcefactory.basic import BasicSourceFactory from zc.sourcefactory.contextual import BasicContextualSourceFactory from zc.sourcefactory.source import FactoredContextualSource from zc.sourcefactory.interfaces import IContextualSource from waeup.ikoba.sourcefactory import SmartBasicContextualSourceFactory from waeup.ikoba.interfaces import ( SimpleIkobaVocabulary, SUBMITTED, VERIFIED, APPROVED ) from waeup.ikoba.interfaces import MessageFactory as _ from waeup.ikoba.utils.helpers import get_sorted_preferred from waeup.ikoba.utils.countries import COUNTRIES from waeup.ikoba.payments.currencies import ISO_4217_CURRENCIES #: a tuple of tuples (, ) with Nigeria first. COUNTRIES = get_sorted_preferred(COUNTRIES, ['NG']) nats_vocab = SimpleIkobaVocabulary(*COUNTRIES) class GenderSource(BasicSourceFactory): """A gender source delivers basically a mapping ``{'m': 'Male', 'f': 'Female'}`` Using a source, we make sure that the tokens (which are stored/expected for instance from CSV files) are something one can expect and not cryptic IntIDs. """ def getValues(self): return ['m', 'f'] def getToken(self, value): return value[0].lower() def getTitle(self, value): if value == 'm': return _('male') if value == 'f': return _('female') class RegNumNotInSource(ValidationError): """Registration number in use. """ # The docstring of ValidationErrors is used as error description # by zope.formlib. pass class EmailNotInSource(ValidationError): """Email address in use. """ pass class RegNumberSource(object): """A source that accepts any entry for a certain field if not used already. Using this kind of source means a way of setting an invariant. We accept a value iff: - the value cannot be found in catalog or - the value can be found as part of some item but the bound item is the context object itself. """ implements(ISource) cat_name = 'customers_catalog' field_name = 'reg_number' validation_error = RegNumNotInSource comp_field = 'customer_id' def __init__(self, context): self.context = context return def __contains__(self, value): """We accept all values not already given to other customers. """ cat = queryUtility(ICatalog, self.cat_name) if cat is None: return True kw = {self.field_name: (value, value)} results = cat.searchResults(**kw) for entry in results: if not hasattr(self.context, self.comp_field): # we have no context with comp_field (most probably # while adding a new object, where the container is # the context) which means that the value was given # already to another object (as _something_ was found in # the catalog with that value). Fail on first round. raise self.validation_error(value) if getattr(entry, self.comp_field) != getattr( self.context, self.comp_field): # An entry already given to another customer is not in our # range of acceptable values. raise self.validation_error(value) #return False return True def contextual_reg_num_source(context): source = RegNumberSource(context) return source directlyProvides(contextual_reg_num_source, IContextSourceBinder) class EmailSource(RegNumberSource): field_name = 'email' validation_error = EmailNotInSource def contextual_email_source(context): source = EmailSource(context) return source directlyProvides(contextual_email_source, IContextSourceBinder) class ConCatProductSource(BasicContextualSourceFactory): """A contract category product source delivers all products which belong to a certain contract_category. """ def getValues(self, context): concat = getattr(context, 'contract_category', None) products = grok.getSite()['products'].values() if not concat: return products resultlist = [ value for value in products if value.contract_category == concat] return resultlist def getToken(self, context, value): return value.product_id def getTitle(self, context, value): return "%s - %s" % (value.product_id, value.title) class ConCatActiveProductSource(ConCatProductSource): """A contract category product source delivers all products which belong to a certain contract_category. """ def getValues(self, context): concat = getattr(context, 'contract_category', None) products = grok.getSite()['products'].values() if not concat: return products resultlist = [ value for value in products if value.contract_category == concat and value.active ] return resultlist class CustomerDocumentSource(BasicContextualSourceFactory): """A customer document source delivers all submitted and verified documents of the context customer. """ def getValues(self, context): # When checking conversion during import in create mode, # contracts do not belong to customers. # Thus all portal documents must be returned. # Attention: In create mode documents of other customers # can be assigned. ConstraintNotSatisfied is not raised. user_id = getattr(context, 'user_id', None) # Alternative implementation: #if not user_id: # return [] catalog = getUtility(ICatalog, name='documents_catalog') results = catalog.searchResults(user_id=(user_id, user_id)) resultlist = [ value for value in results if value.state in (SUBMITTED, VERIFIED)] return resultlist def getToken(self, context, value): return value.document_id def getTitle(self, context, value): return "%s - %s" % (value.document_id[:9], value.title) class IProductOptionSource(IContextualSource): """A source operating in context. This is a marker interface needed for the registration of the IkobaSequenceWidget when using the List-Choice-ProductOptionSourceFactory field combination primarily in IContract as a replacement for the SourceOrderedMultiSelectWidget. """ class ProductOptionSource(FactoredContextualSource): implements(IProductOptionSource) class ProductOptionSourceFactory(BasicContextualSourceFactory): """A product option source delivers all options belonging to a selected product. """ source_class = ProductOptionSource def getValues(self, context): options = [] recent_options = getattr( getattr(context, 'product_object', None), 'options', []) # Check and add stored options first before adding recent # product options. if context.product_options: for stored_option in context.product_options: if stored_option not in recent_options: options.append(stored_option) options += recent_options return options def getToken(self, context, value): return "%s_%s_%s" % (value.title, value.fee, value.currency) def getTitle(self, context, value): currency = ISO_4217_CURRENCIES[value.currency][1] return "%s @ %s %s" % (value.title, value.fee, currency) class IRefereeSource(IContextualSource): """A source operating in context. This is a marker interface needed for the registration of the IkobaSequenceWidget when using the List-Choice-RefereeSourceFactory field combination primarily in ICustomer as a replacement for the SourceOrderedMultiSelectWidget. """ class RefereeSource(FactoredContextualSource): implements(IRefereeSource) class RefereeSourceFactory(BasicContextualSourceFactory): """A referee source delivers all registered and approved customers. """ source_class = RefereeSource def getValues(self, context): user_id = getattr(getattr(context, 'customer', None), 'user_id', None) catalog = getUtility(ICatalog, name='customers_catalog') results = catalog.searchResults(state=(APPROVED, APPROVED)) return list(results) def getToken(self, context, value): return value.customer_id def getTitle(self, context, value): return "%s (%s)" % (value.display_fullname, value.customer_id[:9])