diff --git a/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/ERP5Site_filterParameterList.xml b/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/ERP5Site_filterParameterList.xml index 37ff9f3d56949004a7e4af5f0fd2da91704d5715..e052910a4112033380277ecdffcfa56c98d3a0e4 100644 --- a/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/ERP5Site_filterParameterList.xml +++ b/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/ERP5Site_filterParameterList.xml @@ -52,6 +52,7 @@ <key> <string>_body</string> </key> <value> <string>kept_names = (\'editable_mode\', \'ignore_layout\', # erp5_web\n \'selection_name\', \'selection_index\', # list mode\n + \'selection_key\', # list mode\n \'bt_list\', # business template installation system\n \'ignore_hide_rows\',\n )\n diff --git a/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/ListBox_asHTML.xml b/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/ListBox_asHTML.xml index 15fb6230be3b03eb679fe53e36584b1821610f03..fec16bb0ac854e38ca0e887dd763964bbe41cd88 100644 --- a/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/ListBox_asHTML.xml +++ b/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/ListBox_asHTML.xml @@ -546,6 +546,11 @@ </tbody>\n \n </table>\n + <input type="hidden" name="selection_name_selection_key" value="md5"\n + tal:define="selection_key here/getSelectionKey"\n + tal:condition="selection_key"\n + tal:attributes="name string:${selection_name}_selection_key;\n + value selection_key" />\n </div>\n \n <div class="listbox-footer">\n diff --git a/product/ERP5Form/ListBox.py b/product/ERP5Form/ListBox.py index 614dbafe34ea4d5fe7aa821658c672e32006d8df..90cc8f0f23458dccd8bc3f6a06bb71a7b8fb6c41 100644 --- a/product/ERP5Form/ListBox.py +++ b/product/ERP5Form/ListBox.py @@ -1196,7 +1196,20 @@ class ListBoxRenderer: if self.getListMethodName(): # Update parameters, only if list_method is defined. # (i.e. do not update parameters in listboxes intended to show a previously defined selection. - params.update(self.request.form) + listbox_prefix = '%s_' % self.getId() + for k, v in self.request.form.iteritems(): + # Ignore parameters for other listboxes and selection keys. + if 'listbox_' in k or k.endswith('selection_key'): + continue + elif k.startswith(listbox_prefix): + k = k[len(listbox_prefix):] + # <listbox_field_id>_uid is already handled in + # ListBoxValidator.validate() and putting uid in selection + # will limit the contents for the selection. + if k != 'uid': + params[k] = v + else: + params[k] = v for k, v in self.getDefaultParamList(): params.setdefault(k, v) @@ -2102,6 +2115,11 @@ class ListBoxRenderer: """ return self.render(**kw) + def getSelectionKey(self): + selection_tool = self.getSelectionTool() + selection_name = self.getSelectionName() + return selection_tool.getAnonymousSelectionKey(selection_name) + class ListBoxRendererLine: """This class describes a line in a ListBox to assist ListBoxRenderer. """ @@ -2447,6 +2465,9 @@ class ListBoxHTMLRendererLine(ListBoxRendererLine): params.extend(('selection_name=%s' % selection_name, 'selection_index=%s' % self.index, 'reset:int=1')) + selection_tool = self.getObject().getPortalObject().portal_selections + if selection_tool._isAnonymous(): + params.append('selection_key=%s' % selection.getAnonymousSelectionKey()) if params: url = '%s?%s' % (url, '&'.join(params)) except AttributeError: diff --git a/product/ERP5Form/Selection.py b/product/ERP5Form/Selection.py index 967fbc15a7545a99559839417d66b82ee2aed009..1924502c03b70381964e01ea60c010b942d16f4b 100644 --- a/product/ERP5Form/Selection.py +++ b/product/ERP5Form/Selection.py @@ -33,6 +33,7 @@ from OFS.Traversable import Traversable from AccessControl import ClassSecurityInfo from Products.ERP5Type import Permissions as ERP5Permissions from Products.PythonScripts.Utility import allow_class +from hashlib import md5 # Put a try in front XXX from Products.CMFCategory.Category import Category @@ -368,6 +369,10 @@ class Selection(Acquisition.Implicit, Traversable, Persistent): def getReportTreeMode(self): return getattr(self, 'report_tree_mode', 0) + security.declarePublic('getAnonymousSelectionKey') + def getAnonymousSelectionKey(self): + return md5(repr(dict([(k, v) for k, v in self.__dict__.iteritems() if k != 'index']))).hexdigest() + InitializeClass(Selection) allow_class(Selection) diff --git a/product/ERP5Form/Tool/SelectionTool.py b/product/ERP5Form/Tool/SelectionTool.py index 94892074831b257dbaa0238646a2153ec24adf86..de1e991fa1a902130f01e8b51ab0194fc94bf7d6 100644 --- a/product/ERP5Form/Tool/SelectionTool.py +++ b/product/ERP5Form/Tool/SelectionTool.py @@ -32,7 +32,6 @@ """ from OFS.SimpleItem import SimpleItem -from Products.CMFCore.utils import UniqueObject from Products.ERP5Type.Globals import InitializeClass, DTMLFile, PersistentMapping, get_request from AccessControl import ClassSecurityInfo from Products.ERP5Type.Tool.BaseTool import BaseTool @@ -43,8 +42,6 @@ from Products.ERP5Form.Selection import Selection, DomainSelection from ZPublisher.HTTPRequest import FileUpload from hashlib import md5 import string, re -from time import time -from random import random from urlparse import urlsplit, urlunsplit from zLOG import LOG, INFO, WARNING from Acquisition import aq_base @@ -273,6 +270,17 @@ class SelectionTool( BaseTool, SimpleItem ): return None return selection(method=method, context=context, REQUEST=REQUEST, params=params) + def _getRequest(self, REQUEST=None): + if REQUEST is None: + REQUEST = getattr(self, 'REQUEST', None) + return REQUEST + + def _getSelectionKeyFromRequest(self, selection_name, REQUEST): + REQUEST = self._getRequest(REQUEST=REQUEST) + if REQUEST is not None: + return REQUEST.get('%s_selection_key' % selection_name, None) or \ + REQUEST.get('selection_key', None) + security.declareProtected(ERP5Permissions.View, 'getSelectionFor') def getSelectionFor(self, selection_name, REQUEST=None): """ @@ -283,6 +291,11 @@ class SelectionTool( BaseTool, SimpleItem ): if not selection_name: return None selection = self._getSelectionFromContainer(selection_name) + if selection is None and self._isAnonymous(): + selection_key = self._getSelectionKeyFromRequest(selection_name, REQUEST) + if selection_key is not None: + selection = self.getAnonymousSelection(selection_key, selection_name) + self._setSelectionToContainer(selection_name, selection) if selection is not None: return selection.__of__(self) @@ -296,19 +309,21 @@ class SelectionTool( BaseTool, SimpleItem ): """ if not selection_name: return - anonymous_uid = REQUEST and REQUEST.get('anonymous_uid', None) - if anonymous_uid is not None: - self.REQUEST.response.setCookie('anonymous_uid', anonymous_uid, - path='/') - if not (selection_object is None or selection_name == selection_object.name): LOG('SelectionTool', WARNING, "Selection not set: new Selection name ('%s') differs from existing one ('%s')" % \ (selection_name, selection_object.name)) - elif self.getSelectionFor(selection_name) != selection_object: + elif self.getSelectionFor(selection_name, REQUEST=REQUEST) != selection_object: self._setSelectionToContainer(selection_name, selection_object) + if selection_object is None and self._isAnonymous(): + REQUEST = self._getRequest(REQUEST=REQUEST) + for key in ('%s_selection_key' % selection_name, 'selection_key'): + try: + del REQUEST.form[key] + except KeyError: + pass security.declareProtected(ERP5Permissions.View, 'getSelectionParamsFor') def getSelectionParamsFor(self, selection_name, params=None, REQUEST=None): @@ -447,7 +462,10 @@ class SelectionTool( BaseTool, SimpleItem ): """ selection = self.getSelectionFor(selection_name, REQUEST=REQUEST) if selection: - return selection.getListUrl() + url = selection.getListUrl() + if self._isAnonymous() and '?' in url: + url += '&selection_key=%s' % self._getSelectionKeyFromRequest(selection_name, REQUEST) + return url else: return None @@ -669,88 +687,30 @@ class SelectionTool( BaseTool, SimpleItem ): """ Access first item in a selection """ - if not REQUEST: - REQUEST = get_request() - selection = self.getSelectionFor(selection_name, REQUEST=REQUEST) - if selection: - method = self.unrestrictedTraverse(selection.method_path) - selection_list = selection(method = method, context=self, REQUEST=REQUEST) - if len(selection_list): - o = selection_list[0] - url = o.absolute_url() - form_id = self._getExistingFormId(o.getObject(), form_id) - else: - url = REQUEST.getURL() - else: - url = REQUEST.getURL() - ignore_layout = int(REQUEST.get('ignore_layout', 0)) - if form_id != 'view': - url += '/%s' % form_id - url += '?selection_index=%s&selection_name=%s' % (0, selection_name) - if ignore_layout: - url += '&ignore_layout:int=1' - REQUEST.RESPONSE.redirect(url) + return self._redirectToIndex(0, selection_name, form_id, REQUEST) security.declareProtected(ERP5Permissions.View, 'viewLast') def viewLast(self, selection_index='', selection_name='', form_id='view', REQUEST=None): """ Access last item in a selection """ - if not REQUEST: - REQUEST = get_request() - selection = self.getSelectionFor(selection_name, REQUEST=REQUEST) - if selection: - method = self.unrestrictedTraverse(selection.method_path) - selection_list = selection(method = method, context=self, REQUEST=REQUEST) - if len(selection_list): - o = selection_list[-1] - url = o.absolute_url() - form_id = self._getExistingFormId(o.getObject(), form_id) - else: - url = REQUEST.getURL() - else: - url = REQUEST.getURL() - ignore_layout = int(REQUEST.get('ignore_layout', 0)) - if form_id != 'view': - url += '/%s' % form_id - url += '?selection_index=%s&selection_name=%s' % (-1, selection_name) - if ignore_layout: - url += '&ignore_layout:int=1' - REQUEST.RESPONSE.redirect(url) + return self._redirectToIndex(-1, selection_name, form_id, REQUEST) security.declareProtected(ERP5Permissions.View, 'viewNext') def viewNext(self, selection_index='', selection_name='', form_id='view', REQUEST=None): """ Access next item in a selection """ - if not REQUEST: - REQUEST = get_request() - selection = self.getSelectionFor(selection_name, REQUEST=REQUEST) - if selection: - method = self.unrestrictedTraverse(selection.method_path) - selection_list = selection(method = method, context=self, REQUEST=REQUEST) - if len(selection_list): - o = selection_list[(int(selection_index) + 1) % len(selection_list)] - url = o.absolute_url() - form_id = self._getExistingFormId(o.getObject(), form_id) - else: - url = REQUEST.getURL() - else: - url = REQUEST.getURL() - ignore_layout = int(REQUEST.get('ignore_layout', 0)) - if form_id != 'view': - url += '/%s' % form_id - url += '?selection_index=%s&selection_name=%s' % (int(selection_index)+1, - selection_name) - if ignore_layout: - url += '&ignore_layout:int=1' - REQUEST.RESPONSE.redirect(url) + return self._redirectToIndex(int(selection_index) + 1, selection_name, form_id, REQUEST) security.declareProtected(ERP5Permissions.View, 'viewPrevious') def viewPrevious(self, selection_index='', selection_name='', form_id='view', REQUEST=None): """ Access previous item in a selection """ + return self._redirectToIndex(int(selection_index) - 1, selection_name, form_id, REQUEST) + + def _redirectToIndex(self, selection_index, selection_name, form_id, REQUEST): if not REQUEST: REQUEST = get_request() selection = self.getSelectionFor(selection_name, REQUEST=REQUEST) @@ -758,7 +718,9 @@ class SelectionTool( BaseTool, SimpleItem ): method = self.unrestrictedTraverse(selection.method_path) selection_list = selection(method = method, context=self, REQUEST=REQUEST) if len(selection_list): - o = selection_list[(int(selection_index) - 1) % len(selection_list)] + if selection_index > 0: + selection_index = selection_index % len(selection_list) + o = selection_list[selection_index] url = o.absolute_url() form_id = self._getExistingFormId(o.getObject(), form_id) else: @@ -768,13 +730,13 @@ class SelectionTool( BaseTool, SimpleItem ): ignore_layout = int(REQUEST.get('ignore_layout', 0)) if form_id != 'view': url += '/%s' % form_id - url += '?selection_index=%s&selection_name=%s' % (int(selection_index)-1, - selection_name) + url += '?selection_index=%s&selection_name=%s' % (selection_index, selection_name) if ignore_layout: url += '&ignore_layout:int=1' + if self._isAnonymous(): + url += '&selection_key=%s' % self.getAnonymousSelectionKey(selection_name, REQUEST=REQUEST) REQUEST.RESPONSE.redirect(url) - # ListBox related methods security.declareProtected(ERP5Permissions.View, 'firstPage') @@ -1467,12 +1429,6 @@ class SelectionTool( BaseTool, SimpleItem ): if user_id is not None: return user_id user_id = self.portal_membership.getAuthenticatedMember().getUserName() - if user_id == 'Anonymous User' and self.getAnonymousStorage() is not None: - anonymous_uid = self.REQUEST.get('anonymous_uid', None) - if anonymous_uid is None: - anonymous_uid = md5('%s.%s' % (time(), random())).hexdigest() - self.REQUEST['anonymous_uid'] = anonymous_uid - user_id = 'Anonymous:%s' % anonymous_uid tv['_user_id'] = user_id return user_id @@ -1498,6 +1454,25 @@ class SelectionTool( BaseTool, SimpleItem ): temporary_selection_dict[selection_name]: temporary_selection_dict[selection_name].pop() + def getAnonymousSelection(self, key, selection_name): + container_id = '_v_anonymous_selection_container' + storage = self.getAnonymousStorage() or self.getStorage() + container = self._getContainerFromStorage(container_id, storage) + return container.getSelection(key, selection_name) + + def getAnonymousSelectionKey(self, selection_name, REQUEST=None): + if not self._isAnonymous(): + return '' + selection = self.getSelectionFor(selection_name, REQUEST=REQUEST) + if selection is None: + return '' + key = selection.getAnonymousSelectionKey() + container_id = '_v_anonymous_selection_container' + storage = self.getAnonymousStorage() or self.getStorage() + container = self._getContainerFromStorage(container_id, storage) + container.setSelection(key, selection_name, selection) + return key + def _getSelectionFromContainer(self, selection_name): user_id = self._getUserId() if user_id is None: return None @@ -1540,15 +1515,20 @@ class SelectionTool( BaseTool, SimpleItem ): self.getTemporarySelectionDict().keys())) def _isAnonymous(self): - return self.portal_membership.isAnonymousUser() + return self._getUserId() == 'Anonymous User' def _getContainer(self): if self._isAnonymous(): - container_id = '_v_anonymous_selection_container' - storage = self.getAnonymousStorage() or self.getStorage() + tv = getTransactionalVariable() + storage = tv.setdefault('_transactional_selection_container', {}) + container = TransactionalCacheContainer(storage) + return container else: container_id = '_v_selection_container' storage = self.getStorage() + return self._getContainerFromStorage(container_id, storage) + + def _getContainerFromStorage(self, container_id, storage): container = getattr(aq_base(self), container_id, None) if container is None: if storage.startswith('portal_memcached/'): @@ -1567,10 +1547,11 @@ class SelectionTool( BaseTool, SimpleItem ): InitializeClass( SelectionTool ) -class MemcachedContainer(object): +class BaseContainer(object): def __init__(self, container): self._container = container +class MemcachedContainer(BaseContainer): def getSelectionNameList(self, user_id): return [] @@ -1589,10 +1570,11 @@ class MemcachedContainer(object): def deleteGlobalSelection(self, user_id, selection_name): pass -class PersistentMappingContainer(object): - def __init__(self, container): - self._container = container +class TransactionalCacheContainer(MemcachedContainer): + def setSelection(self, user_id, selection_name, selection): + self._container.__setitem__('%s-%s' % (user_id, selection_name), aq_base(selection)) +class PersistentMappingContainer(BaseContainer): def getSelectionNameList(self, user_id): try: return self._container[user_id].keys()