diff --git a/CHANGES.rst b/CHANGES.rst index c63d6d4f..9c8d639a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog 0.11 (unreleased) ----------------- +- Add a PlonePAS Plugin for computed user properties. + [pcdummy] + - Plone 5 Update, use separate styles/less [agitator] diff --git a/base.cfg b/base.cfg index 0fcb8169..ae9c1eb5 100644 --- a/base.cfg +++ b/base.cfg @@ -33,6 +33,7 @@ eggs = plone.reload bda.plone.shop ipdb + Products.PdbDebugMode # Products.PrintingMailHost zcml = plone.reload diff --git a/src/bda/plone/shop/__init__.py b/src/bda/plone/shop/__init__.py index d6c5e46d..83d99f4f 100644 --- a/src/bda/plone/shop/__init__.py +++ b/src/bda/plone/shop/__init__.py @@ -1,5 +1,16 @@ from bda.plone.shop import permissions # nopep8 +from bda.plone.shop.user.properties import UserPropertiesPASPlugin +from Products.PluggableAuthService.PluggableAuthService import \ + registerMultiPlugin from zope.i18nmessageid import MessageFactory message_factory = MessageFactory('bda.plone.shop') + + +def initialize(context): + """ + Initializer called when used as a Zope 2 product. + """ + # Add to PAS menu + registerMultiPlugin(UserPropertiesPASPlugin.meta_type) \ No newline at end of file diff --git a/src/bda/plone/shop/configure.zcml b/src/bda/plone/shop/configure.zcml index 82bc1721..02775f5d 100644 --- a/src/bda/plone/shop/configure.zcml +++ b/src/bda/plone/shop/configure.zcml @@ -1,10 +1,13 @@ + + diff --git a/src/bda/plone/shop/profiles/base/metadata.xml b/src/bda/plone/shop/profiles/base/metadata.xml index 910b8bd8..adb522df 100644 --- a/src/bda/plone/shop/profiles/base/metadata.xml +++ b/src/bda/plone/shop/profiles/base/metadata.xml @@ -1,6 +1,6 @@ - 5 + 6 profile-plone.app.registry:default profile-collective.z3cform.datagridfield:default diff --git a/src/bda/plone/shop/profiles/plone4/metadata.xml b/src/bda/plone/shop/profiles/plone4/metadata.xml index 6a19adc9..6fd8af54 100644 --- a/src/bda/plone/shop/profiles/plone4/metadata.xml +++ b/src/bda/plone/shop/profiles/plone4/metadata.xml @@ -1,6 +1,6 @@ - 5 + 6 profile-bda.plone.shop:install-base diff --git a/src/bda/plone/shop/profiles/plone5/metadata.xml b/src/bda/plone/shop/profiles/plone5/metadata.xml index 6a19adc9..6fd8af54 100644 --- a/src/bda/plone/shop/profiles/plone5/metadata.xml +++ b/src/bda/plone/shop/profiles/plone5/metadata.xml @@ -1,6 +1,6 @@ - 5 + 6 profile-bda.plone.shop:install-base diff --git a/src/bda/plone/shop/setuphandlers.py b/src/bda/plone/shop/setuphandlers.py index 440e2bf4..2696c808 100644 --- a/src/bda/plone/shop/setuphandlers.py +++ b/src/bda/plone/shop/setuphandlers.py @@ -1,9 +1,89 @@ # -*- coding:utf-8 -*- +import logging + from Products.CMFPlone import interfaces as Plone from Products.CMFQuickInstallerTool import interfaces as QuickInstaller +from Products.PluggableAuthService.interfaces.plugins import IPropertiesPlugin +from bda.plone.shop.user.properties import UserPropertiesPASPlugin +from bda.plone.shop.user.properties import PAS_ID +from plone import api from zope.interface import implementer +PAS_TITLE = 'bda.plone.shop plugin' + + +logger = logging.getLogger('bda.plone.shop') + + +def add_plugin(pas, plugin_id=PAS_ID): + """ + Install and activate bda.plone.shop user properties PAS plugin + """ + # Skip if already installed (activation is assumed). + installed = pas.objectIds() + if plugin_id in installed: + return PAS_TITLE + " already installed." + + # Install the plugin + plugin = UserPropertiesPASPlugin(plugin_id, title=PAS_TITLE) + pas._setObject(plugin_id, plugin) + + # get plugin acquisition wrapped + plugin = pas[plugin.getId()] + + # Activate the Plugin + pas.plugins.activatePlugin(IPropertiesPlugin, plugin.getId()) + + # Make it the last Plugin in the list of PropertiesPlugin - FIFO. + pas.plugins.movePluginsDown( + IPropertiesPlugin, + [x[0] for x in pas.plugins.listPlugins(IPropertiesPlugin)[:-1]], + ) + + return PAS_TITLE + " installed." + + +def remove_plugin(pas, plugin_id=PAS_ID): + """ + Deactivate and uninstall bda.plone.shop user properties PAS plugin + """ + + # Skip if already uninstalled (deactivation is assumed). + installed = pas.objectIds() + if plugin_id not in installed: + return PAS_TITLE + " not installed." + + plugin = UserPropertiesPASPlugin(plugin_id, title=PAS_TITLE) + + # get plugin acquisition wrapped + plugin = pas[plugin.getId()] + + # Deactivate the plugin + pas.plugins.deactivatePlugin(IPropertiesPlugin, plugin.getId()) + + # And finaly uninstall it + pas._delObject(plugin_id, plugin) + + return PAS_TITLE + " uninstalled." + + +def install(context): + """ + Install the PAS plugin. + """ + pas = api.portal.get_tool(name='acl_users') + logger.info(add_plugin(pas)) + + +def uninstall(context): + """ + Remove the PAS plugin. + """ + pas = api.portal.get_tool(name='acl_users') + logger.info(remove_plugin(pas)) + + @implementer(Plone.INonInstallable) class HiddenProfiles(object): diff --git a/src/bda/plone/shop/upgrades/__init__.py b/src/bda/plone/shop/upgrades/__init__.py index ab92e957..341a2e2c 100644 --- a/src/bda/plone/shop/upgrades/__init__.py +++ b/src/bda/plone/shop/upgrades/__init__.py @@ -1,7 +1,10 @@ -from zope.component import getUtility +import logging + +from bda.plone.shop.setuphandlers import add_plugin +from plone import api from plone.registry.interfaces import IRegistry +from zope.component import getUtility -import logging logger = logging.getLogger('bda.plone.shop UPGRADE') @@ -15,3 +18,8 @@ def update_notification_text_registry_entries(ctx=None): del registry.records[key] key = 'bda.plone.shop.interfaces.IPaymentTextSettings.payment_text' del registry.records[key] + + +def install_userproperties_pas_plugin(context): + pas = api.portal.get_tool(name='acl_users') + logger.info(add_plugin(pas)) diff --git a/src/bda/plone/shop/upgrades/configure.zcml b/src/bda/plone/shop/upgrades/configure.zcml index ea2d320a..f37db8cb 100644 --- a/src/bda/plone/shop/upgrades/configure.zcml +++ b/src/bda/plone/shop/upgrades/configure.zcml @@ -48,4 +48,15 @@ + + + + + + diff --git a/src/bda/plone/shop/user/properties.py b/src/bda/plone/shop/user/properties.py new file mode 100644 index 00000000..71f04f20 --- /dev/null +++ b/src/bda/plone/shop/user/properties.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +from AccessControl.class_init import InitializeClass +from Products.CMFPlone.utils import safe_unicode +from Products.PluggableAuthService.interfaces.plugins import IPropertiesPlugin +from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin +from Products.PluggableAuthService.utils import classImplements +from bda.plone.checkout.vocabularies import get_pycountry_name +from zope.interface import implementer + + +PAS_ID = 'bda.plone.shop' + + +@implementer(IPropertiesPlugin) +class UserPropertiesPASPlugin(BasePlugin): + """ An implementer of IPropertiesPlugin which + provides the method getPropertiesForUser, this method + adds computed properties to PlonePAS which we remove + at another place. + """ + + meta_type = 'bda.plone.shop user properties' + + def __init__(self, id, title=None): + self._setId(id) + self.title = title + + def _getPropertSheetsFromUser(self, user): + """ Get PropertySheets from a IPropertiedUser without + the current sheet, so we dont have recursions. + """ + sheet_ids = user.listPropertysheets() + + result = [] + for sheet_id in sheet_ids: + if sheet_id != PAS_ID: + result.append(user.getPropertysheet(sheet_id)) + + return result + + def getPropertiesForUser(self, user, request=None): + """ This can't use plone.api.user.get(user.getId()) as it would + lead to a recursion. + """ + + sheets = self._getPropertSheetsFromUser(user) + + def getProperty(id, default): + for sheet in sheets: + if sheet.hasProperty(id): + # Return the first one that has the property. + return safe_unicode(sheet.getProperty(id)) + + return safe_unicode(default) + + first = getProperty('firstname', u'') + last = getProperty('lastname', u'') + street = getProperty('street', u'') + zip_code = getProperty('zip', u'') + city = getProperty('city', u'') + country = getProperty('country', u'') + + try: + country = country and get_pycountry_name(country) or '' + except KeyError: + country = '' + + join_list = [street, '{0} {1}'.format(zip_code, city), country] + + return { + 'fullname': u'%s%s%s' % ( + first, + first and last and u' ' or u'', + last, + ), + + 'location': u', '.join([it for it in join_list if it]), + } + +classImplements(UserPropertiesPASPlugin, IPropertiesPlugin) +InitializeClass(UserPropertiesPASPlugin) diff --git a/src/bda/plone/shop/user/userdata.py b/src/bda/plone/shop/user/userdata.py index a79a0504..52420669 100644 --- a/src/bda/plone/shop/user/userdata.py +++ b/src/bda/plone/shop/user/userdata.py @@ -13,7 +13,6 @@ from plone.z3cform.fieldsets import extensible from z3c.form import field from bda.plone.checkout.interfaces import ICheckoutFormPresets -from bda.plone.checkout.vocabularies import get_pycountry_name from bda.plone.shop.interfaces import IShopExtensionLayer from bda.plone.shop import message_factory as _ @@ -164,24 +163,6 @@ class ICustomer(model.Schema): class UserDataSchemaAdapter(AccountPanelSchemaAdapter): schema = ICustomer - @property - def fullname(self): - first = self._getProperty('firstname') - last = self._getProperty('lastname') - return u'%s%s' % (first and first or '', - first and last and ' ' or '', - last and last or '') - - @property - def location(self): - street = self._getProperty('street') - zip = self._getProperty('zip') - city = self._getProperty('city') - country = self._getProperty('country') - country = country and get_pycountry_name(country) or '' - join_list = [street, '{0} {1}'.format(zip, city), country] - return ', '.join([it for it in join_list if it]) - class UserDataPanelExtender(extensible.FormExtender): adapts(Interface, IShopExtensionLayer, UserDataPanel)