Skip to content

Commit

Permalink
Merge pull request #72 from bluedynamics/ticket_66
Browse files Browse the repository at this point in the history
Fixes ticket #66 by implementing a PlonePAS plugin for properties.
  • Loading branch information
rnixx committed May 17, 2016
2 parents b9d622b + 7dad14a commit 63dbd81
Show file tree
Hide file tree
Showing 13 changed files with 205 additions and 25 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
1 change: 1 addition & 0 deletions base.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ eggs =
plone.reload
bda.plone.shop
ipdb
Products.PdbDebugMode
# Products.PrintingMailHost
zcml =
plone.reload
Expand Down
11 changes: 11 additions & 0 deletions src/bda/plone/shop/__init__.py
Original file line number Diff line number Diff line change
@@ -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)
3 changes: 3 additions & 0 deletions src/bda/plone/shop/configure.zcml
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five"
xmlns:zcml="http://namespaces.zope.org/zcml"
xmlns:i18n="http://namespaces.zope.org/i18n"
i18n_domain="bda.plone.shop">

<i18n:registerTranslations directory="locales" />

<five:registerPackage package="." initialize=".initialize" />

<!--
Set this interface on desired content class in order to enable content
action turning object into buyable.
Expand Down
3 changes: 2 additions & 1 deletion src/bda/plone/shop/profiles.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
title="bda.plone.shop base"
directory="profiles/base"
description="bda.plone.shop"
provides="Products.GenericSetup.interfaces.EXTENSION" />
provides="Products.GenericSetup.interfaces.EXTENSION"
post_handler="bda.plone.shop.setuphandlers.install" />

<!-- Hide profiles/products from Quick Installer -->
<utility factory=".setuphandlers.HiddenProfiles" name="bda.plone.shop" />
Expand Down
2 changes: 1 addition & 1 deletion src/bda/plone/shop/profiles/base/metadata.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<metadata>
<version>5</version>
<version>6</version>
<dependencies>
<dependency>profile-plone.app.registry:default</dependency>
<dependency>profile-collective.z3cform.datagridfield:default</dependency>
Expand Down
2 changes: 1 addition & 1 deletion src/bda/plone/shop/profiles/plone4/metadata.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<metadata>
<version>5</version>
<version>6</version>
<dependencies>
<dependency>profile-bda.plone.shop:install-base</dependency>
</dependencies>
Expand Down
2 changes: 1 addition & 1 deletion src/bda/plone/shop/profiles/plone5/metadata.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<metadata>
<version>5</version>
<version>6</version>
<dependencies>
<dependency>profile-bda.plone.shop:install-base</dependency>
</dependencies>
Expand Down
80 changes: 80 additions & 0 deletions src/bda/plone/shop/setuphandlers.py
Original file line number Diff line number Diff line change
@@ -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):

Expand Down
12 changes: 10 additions & 2 deletions src/bda/plone/shop/upgrades/__init__.py
Original file line number Diff line number Diff line change
@@ -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')

Expand All @@ -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))
11 changes: 11 additions & 0 deletions src/bda/plone/shop/upgrades/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,15 @@

</genericsetup:upgradeSteps>

<genericsetup:upgradeSteps
source="5"
destination="6"
profile="bda.plone.shop:default">

<genericsetup:upgradeStep
title="Install a PAS Plugin for computed user properties"
handler=".install_userproperties_pas_plugin" />

</genericsetup:upgradeSteps>

</configure>
81 changes: 81 additions & 0 deletions src/bda/plone/shop/user/properties.py
Original file line number Diff line number Diff line change
@@ -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)
19 changes: 0 additions & 19 deletions src/bda/plone/shop/user/userdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 _

Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 63dbd81

Please sign in to comment.