From 41f9c46565cb4cef9755690c33b877e5a39ff7fa Mon Sep 17 00:00:00 2001 From: Rodrigo Ferreira de Souza Date: Thu, 26 Dec 2013 17:01:22 -0200 Subject: [PATCH 01/63] Make JS reloadTypes variable behaves as a singleton --- CHANGES.rst | 4 ++++ src/collective/cover/static/cover.js | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 820b09d40..2db236043 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,10 @@ There's a frood who really knows where his towel is. 1.0a7 (unreleased) ^^^^^^^^^^^^^^^^^^ +- Add an option to extend JS configuration to reload tile in custom + tiles (outside of collective.cover). + [rodfersou] + - Use plone.api where possible. [hvelarde] diff --git a/src/collective/cover/static/cover.js b/src/collective/cover/static/cover.js index d807e5694..145c35450 100644 --- a/src/collective/cover/static/cover.js +++ b/src/collective/cover/static/cover.js @@ -50,6 +50,7 @@ function TitleMarkupSetup(){ } $(document).ready(function() { + var root = typeof exports !== "undefined" && exports !== null ? exports : this; $(".sortable-tile").liveSortable({ stop:function(event, ui) { @@ -112,8 +113,8 @@ $(document).ready(function() { // Get tile type var tileType = tile.data('tile-type'); // List of tile types that make a page reload - var reloadTypes = ['collective.cover.carousel']; - if(reloadTypes.indexOf(tileType)>-1) { + root.reloadTypes = ['collective.cover.carousel']; + if(root.reloadTypes.indexOf(tileType)>-1) { location.reload(); } else { tile.html(return_value); From 8e8e809b10c80d1bbb1b4e634b83651c9f4ba6b2 Mon Sep 17 00:00:00 2001 From: Rodrigo Ferreira de Souza Date: Fri, 27 Dec 2013 15:23:20 -0200 Subject: [PATCH 02/63] Fixed place where to set reloadTypes variable --- src/collective/cover/static/cover.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/collective/cover/static/cover.js b/src/collective/cover/static/cover.js index 145c35450..8d9f1ff84 100644 --- a/src/collective/cover/static/cover.js +++ b/src/collective/cover/static/cover.js @@ -51,6 +51,7 @@ function TitleMarkupSetup(){ $(document).ready(function() { var root = typeof exports !== "undefined" && exports !== null ? exports : this; + root.reloadTypes = ['collective.cover.carousel']; $(".sortable-tile").liveSortable({ stop:function(event, ui) { @@ -113,7 +114,6 @@ $(document).ready(function() { // Get tile type var tileType = tile.data('tile-type'); // List of tile types that make a page reload - root.reloadTypes = ['collective.cover.carousel']; if(root.reloadTypes.indexOf(tileType)>-1) { location.reload(); } else { From 7a38396e56f3000cac08ca855a1100e71ab924d6 Mon Sep 17 00:00:00 2001 From: Franco Pellegrini Date: Thu, 25 Sep 2014 13:17:01 -0300 Subject: [PATCH 03/63] Start replacing basic list for uuids with a persistentdict --- src/collective/cover/tiles/carousel.py | 19 +---- src/collective/cover/tiles/list.py | 76 ++++++++++++------- .../cover/widgets/textlinessortable.py | 5 +- 3 files changed, 54 insertions(+), 46 deletions(-) diff --git a/src/collective/cover/tiles/carousel.py b/src/collective/cover/tiles/carousel.py index 14a6c5782..8dec0c943 100644 --- a/src/collective/cover/tiles/carousel.py +++ b/src/collective/cover/tiles/carousel.py @@ -56,29 +56,14 @@ class CarouselTile(ListTile): short_name = _(u'msg_short_name_carousel', default=u'Carousel') def populate_with_object(self, obj): - super(CarouselTile, self).populate_with_object(obj) # check permission try: scale = obj.restrictedTraverse('@@images').scale('image') except: scale = None if not scale: return - self.set_limit() - uuid = IUUID(obj, None) - data_mgr = ITileDataManager(self) - - old_data = data_mgr.get() - if data_mgr.get()['uuids']: - uuids = data_mgr.get()['uuids'] - if type(uuids) != list: - uuids = [uuid] - elif uuid not in uuids: - uuids.append(uuid) - - old_data['uuids'] = uuids[:self.limit] - else: - old_data['uuids'] = [uuid] - data_mgr.set(old_data) + + super(CarouselTile, self).populate_with_object(obj) def autoplay(self): if self.data['autoplay'] is None: diff --git a/src/collective/cover/tiles/list.py b/src/collective/cover/tiles/list.py index fd6feeb18..4297f4754 100644 --- a/src/collective/cover/tiles/list.py +++ b/src/collective/cover/tiles/list.py @@ -7,6 +7,7 @@ from collective.cover.tiles.base import IPersistentCoverTile from collective.cover.tiles.base import PersistentCoverTile from collective.cover.tiles.configuration_view import IDefaultConfigureForm +from persistent.mapping import PersistentMapping from plone import api from plone.app.uuid.utils import uuidToObject from plone.directives import form @@ -112,9 +113,12 @@ def results(self): # always get the latest data uuids = ITileDataManager(self).get().get('uuids', None) - results, remove = [], [] + results = list() if uuids: - for uid in uuids: + ordered_uuids = [(k,v) for k,v in uuids.items()] + ordered_uuids.sort(key=lambda x:x[1]['order']) + + for uid in [i[0] for i in ordered_uuids]: obj = uuidToObject(uid) if obj: results.append(obj) @@ -125,14 +129,11 @@ def results(self): brain = catalog.unrestrictedSearchResults(UID=uid) if not brain: # the object was deleted; remove it from the tile - # we deal with this below as you can't modify the - # list directly as it's been used on the for loop - remove.append(uid) - - for uid in remove: - self.remove_item(uid) - logger.debug( - 'Nonexistent object {0} removed from tile'.format(uid)) + self.remove_item(uid) + logger.debug( + 'Nonexistent object {0} removed from ' + 'tile'.format(uid) + ) return results[:self.limit] @@ -159,14 +160,36 @@ def populate_with_uids(self, uuids): data_mgr = ITileDataManager(self) old_data = data_mgr.get() + if old_data['uuids'] is None: + # If there is no content yet, just assign an empty dict + old_data['uuids'] = PersistentMapping() + + uuids_dict = old_data.get('uuids') + if not isinstance(uuids_dict, PersistentMapping): + # Make sure this is a PersistentMapping + uuids_dict = old_data['uuids'] = PersistentMapping() + + if uuids_dict and len(uuids_dict) > self.limit: + # Do not allow adding more objects than the defined limit + return + + order_list = [val.get('order', 0) for key, val in uuids_dict.items()] + if len(order_list) == 0: + # First entry + order = 0 + else: + # Get last order position and increment 1 + order_list.sort() + order = order_list.pop() + 1 + for uuid in uuids: - if old_data['uuids']: - if type(old_data['uuids']) != list: - old_data['uuids'] = [uuid] - elif uuid not in old_data['uuids']: - old_data['uuids'].append(uuid) - else: - old_data['uuids'] = [uuid] + if uuid not in uuids_dict.keys(): + entry = PersistentMapping() + entry['order'] = order + uuids_dict[uuid] = entry + order += 1 + + old_data['uuids'] = uuids_dict data_mgr.set(old_data) notify(ObjectModifiedEvent(self)) @@ -174,25 +197,22 @@ def replace_with_objects(self, uids): if not self.isAllowedToEdit(): raise Unauthorized( _('You are not allowed to add content to this tile')) - self.set_limit() data_mgr = ITileDataManager(self) old_data = data_mgr.get() - if type(uids) == list: - old_data['uuids'] = [i for i in uids][:self.limit] - else: - old_data['uuids'] = [uids] - + # Clean old data + old_data['uuids'] = PersistentMapping() data_mgr.set(old_data) - notify(ObjectModifiedEvent(self)) + # Repopulate with clean list + self.populate_with_uids(uids) def remove_item(self, uid): super(ListTile, self).remove_item(uid) # check permission data_mgr = ITileDataManager(self) old_data = data_mgr.get() - uids = data_mgr.get()['uuids'] - if uid in uids: - del uids[uids.index(uid)] - old_data['uuids'] = uids + uuids = data_mgr.get()['uuids'] + if uid in uuids.keys(): + del uuids[uid] + old_data['uuids'] = uuids data_mgr.set(old_data) def get_uid(self, obj): diff --git a/src/collective/cover/widgets/textlinessortable.py b/src/collective/cover/widgets/textlinessortable.py index f3b6bf88d..470133623 100644 --- a/src/collective/cover/widgets/textlinessortable.py +++ b/src/collective/cover/widgets/textlinessortable.py @@ -31,7 +31,10 @@ def render(self): def sort_results(self): uuids = self.context['uuids'] if uuids: - return [{'obj': uuidToObject(x), 'uuid': x} for x in uuids] + ordered_uuids = [(k,v) for k,v in uuids.items()] + ordered_uuids.sort(key=lambda x:x[1]['order']) + return [{'obj': uuidToObject(x[0]), 'uuid': x[0]} + for x in ordered_uuids] else: return [] From 5c694cef1ae88d2fdad3aab31f74fff312f3d663 Mon Sep 17 00:00:00 2001 From: Franco Pellegrini Date: Fri, 26 Sep 2014 12:56:22 -0300 Subject: [PATCH 04/63] Fix UI for handling the new Data structure and also fix the URL when the added object is an image and needs /view at the end --- src/collective/cover/tiles/carousel.py | 47 +++++++++++++++---- src/collective/cover/tiles/configure.zcml | 3 ++ src/collective/cover/tiles/list.py | 13 +++-- .../cover/tiles/templates/carousel.pt | 7 +-- .../cover/widgets/textlines_sortable_input.pt | 4 ++ .../cover/widgets/textlinessortable.py | 27 ++++++++++- 6 files changed, 84 insertions(+), 17 deletions(-) diff --git a/src/collective/cover/tiles/carousel.py b/src/collective/cover/tiles/carousel.py index 8dec0c943..e9eea211c 100644 --- a/src/collective/cover/tiles/carousel.py +++ b/src/collective/cover/tiles/carousel.py @@ -4,13 +4,18 @@ from collective.cover.interfaces import ITileEditForm from collective.cover.tiles.list import IListTile from collective.cover.tiles.list import ListTile +from collective.cover.widgets.interfaces import ITextLinesSortableWidget from collective.cover.widgets.textlinessortable import TextLinesSortableFieldWidget from plone.autoform import directives as form from plone.tiles.interfaces import ITileDataManager -from plone.uuid.interfaces import IUUID +from Products.CMFCore.utils import getToolByName from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile +from z3c.form.converter import DictMultiConverter from zope import schema +from zope.component import adapts from zope.interface import implements +from zope.schema.interfaces import IDict + # autoplay feature is enabled in view mode only INIT_JS = """$(function() {{ @@ -38,12 +43,6 @@ class ICarouselTile(IListTile): form.no_omit(ITileEditForm, 'uuids') form.widget(uuids=TextLinesSortableFieldWidget) - uuids = schema.List( - title=_(u'Elements'), - value_type=schema.TextLine(), - required=False, - readonly=False, - ) class CarouselTile(ListTile): @@ -62,7 +61,6 @@ def populate_with_object(self, obj): scale = None if not scale: return - super(CarouselTile, self).populate_with_object(obj) def autoplay(self): @@ -71,6 +69,22 @@ def autoplay(self): return self.data['autoplay'] + def get_url(self, item): + portal_properties = getToolByName(self.context, 'portal_properties') + use_view_action = portal_properties.site_properties.getProperty( + 'typesUseViewActionInListings', ()) + url = item.absolute_url() + if item.portal_type in use_view_action: + url = url + '/view' + uuid = self.get_uid(item) + data_mgr = ITileDataManager(self) + data = data_mgr.get() + uuids = data['uuids'] + if uuid in uuids: + if uuids[uuid].get('custom_url', u""): + url = uuids[uuid].get('custom_url') + return url + def init_js(self): if self.is_empty(): # Galleria will display scary error messages when it @@ -79,3 +93,20 @@ def init_js(self): return '' return INIT_JS.format(self.id, str(self.autoplay()).lower()) + + +class UUIDSFieldDataConverter(DictMultiConverter): + """A data converter using the field's ``fromUnicode()`` method.""" + adapts(IDict, ITextLinesSortableWidget) + + def toWidgetValue(self, value): + """Just dispatch it.""" + ordered_uuids = [(k, v) for k, v in value.items()] + ordered_uuids.sort(key=lambda x: x[1]['order']) + return '\r\n'.join([i[0] for i in ordered_uuids]) + + def toFieldValue(self, value): + """Just dispatch it.""" + if not len(value) or not isinstance(value, dict): + return self.field.missing_value + return value diff --git a/src/collective/cover/tiles/configure.zcml b/src/collective/cover/tiles/configure.zcml index 373ed5826..6afeea900 100644 --- a/src/collective/cover/tiles/configure.zcml +++ b/src/collective/cover/tiles/configure.zcml @@ -12,6 +12,9 @@ + + + diff --git a/src/collective/cover/widgets/textlines_sortable_input.pt b/src/collective/cover/widgets/textlines_sortable_input.pt index e5275b778..dd8a0be51 100644 --- a/src/collective/cover/widgets/textlines_sortable_input.pt +++ b/src/collective/cover/widgets/textlines_sortable_input.pt @@ -61,6 +61,10 @@ height thumbnail/height" />

+ + Custom URL +

diff --git a/src/collective/cover/widgets/textlinessortable.py b/src/collective/cover/widgets/textlinessortable.py index 470133623..cdcf6753a 100644 --- a/src/collective/cover/widgets/textlinessortable.py +++ b/src/collective/cover/widgets/textlinessortable.py @@ -31,8 +31,8 @@ def render(self): def sort_results(self): uuids = self.context['uuids'] if uuids: - ordered_uuids = [(k,v) for k,v in uuids.items()] - ordered_uuids.sort(key=lambda x:x[1]['order']) + ordered_uuids = [(k, v) for k, v in uuids.items()] + ordered_uuids.sort(key=lambda x: x[1]['order']) return [{'obj': uuidToObject(x[0]), 'uuid': x[0]} for x in ordered_uuids] else: @@ -45,6 +45,29 @@ def thumbnail(self, item): except: return None + def get_custom_url(self, uuid): + url = u'' + uuids = self.context['uuids'] + if uuid in uuids: + values = uuids.get(uuid) + url = values.get('custom_url', u'') + return url + + def extract(self): + values = self.request.get(self.name).split('\r\n') + uuids = [i for i in values if i] + results = dict() + for index, uuid in enumerate(uuids): + if uuid: + custom_url = self.request.get( + '%s.custom_url.%s' % (self.name, uuid), "" + ) + results[uuid] = { + u'order': unicode(index), + u'custom_url': unicode(custom_url) + } + return results + @zope.interface.implementer(interfaces.IFieldWidget) def TextLinesSortableFieldWidget(field, request): From 3148215481bee730781fbc2ed3a9410fe487bd9d Mon Sep 17 00:00:00 2001 From: Franco Pellegrini Date: Fri, 26 Sep 2014 15:16:15 -0300 Subject: [PATCH 05/63] Allow including elements with no images --- src/collective/cover/testing.py | 2 +- .../cover/tests/test_carousel_tile.py | 35 ++++++++++++++++++- src/collective/cover/tiles/carousel.py | 9 ----- src/collective/cover/tiles/list.py | 6 ++-- 4 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/collective/cover/testing.py b/src/collective/cover/testing.py index 36008236a..2ede3a8f5 100644 --- a/src/collective/cover/testing.py +++ b/src/collective/cover/testing.py @@ -76,7 +76,7 @@ def generate_jpeg(width, height): output = StringIO() image.save(output, format='PNG') - return output.getvalue() + return output def images_are_equal(str1, str2): diff --git a/src/collective/cover/tests/test_carousel_tile.py b/src/collective/cover/tests/test_carousel_tile.py index 3b668d9ba..5f6b00b41 100644 --- a/src/collective/cover/tests/test_carousel_tile.py +++ b/src/collective/cover/tests/test_carousel_tile.py @@ -3,6 +3,8 @@ from collective.cover.tests.base import TestTileMixin from collective.cover.tiles.carousel import CarouselTile from collective.cover.tiles.carousel import ICarouselTile +from persistent.mapping import PersistentMapping +from plone.tiles.interfaces import ITileDataManager from plone.uuid.interfaces import IUUID import unittest @@ -40,6 +42,7 @@ def test_crud(self): # now we add a couple of objects to the list obj1 = self.portal['my-document'] obj2 = self.portal['my-image'] + self.tile.populate_with_object(obj1) self.tile.populate_with_object(obj2) @@ -51,7 +54,7 @@ def test_crud(self): self.assertIn(obj2, self.tile.results()) # next, we replace the list of objects with a different one - obj3 = self.portal['my-news-item'] + obj3 = self.portal['my-image1'] self.tile.replace_with_objects([IUUID(obj3, None)]) # tile's data attribute is cached; reinstantiate it self.tile = self.cover.restrictedTraverse( @@ -66,3 +69,33 @@ def test_crud(self): self.tile = self.cover.restrictedTraverse( '@@{0}/{1}'.format('collective.cover.carousel', 'test')) self.assertTrue(self.tile.is_empty()) + + def test_internal_structure(self): + # we start with an empty tile + self.assertTrue(self.tile.is_empty()) + uuids = ITileDataManager(self.tile).get().get('uuids', None) + + self.assertIsNone(uuids) + + # now we add an image + obj1 = self.portal['my-image'] + + self.tile.populate_with_object(obj1) + + uuids = ITileDataManager(self.tile).get().get('uuids', None) + + self.assertTrue(isinstance(uuids, PersistentMapping)) + self.assertTrue(len(uuids) == 1) + self.assertTrue(uuids[obj1.UID()]['order'] == u'0') + + # now we add a second image + obj2 = self.portal['my-image1'] + + self.tile.populate_with_object(obj2) + + uuids = ITileDataManager(self.tile).get().get('uuids', None) + + self.assertTrue(isinstance(uuids, PersistentMapping)) + self.assertTrue(len(uuids) == 2) + self.assertTrue(uuids[obj1.UID()]['order'] == u'0') + self.assertTrue(uuids[obj2.UID()]['order'] == u'1') diff --git a/src/collective/cover/tiles/carousel.py b/src/collective/cover/tiles/carousel.py index e9eea211c..10cfd2012 100644 --- a/src/collective/cover/tiles/carousel.py +++ b/src/collective/cover/tiles/carousel.py @@ -54,15 +54,6 @@ class CarouselTile(ListTile): is_editable = True short_name = _(u'msg_short_name_carousel', default=u'Carousel') - def populate_with_object(self, obj): - try: - scale = obj.restrictedTraverse('@@images').scale('image') - except: - scale = None - if not scale: - return - super(CarouselTile, self).populate_with_object(obj) - def autoplay(self): if self.data['autoplay'] is None: return True # default value diff --git a/src/collective/cover/tiles/list.py b/src/collective/cover/tiles/list.py index dcb10305b..009a41e31 100644 --- a/src/collective/cover/tiles/list.py +++ b/src/collective/cover/tiles/list.py @@ -177,7 +177,8 @@ def populate_with_uids(self, uuids): # Do not allow adding more objects than the defined limit return - order_list = [val.get('order', 0) for key, val in uuids_dict.items()] + order_list = [int(val.get('order', 0)) + for key, val in uuids_dict.items()] if len(order_list) == 0: # First entry order = 0 @@ -189,7 +190,7 @@ def populate_with_uids(self, uuids): for uuid in uuids: if uuid not in uuids_dict.keys(): entry = PersistentMapping() - entry['order'] = order + entry[u'order'] = unicode(order) uuids_dict[uuid] = entry order += 1 @@ -197,6 +198,7 @@ def populate_with_uids(self, uuids): data_mgr.set(old_data) notify(ObjectModifiedEvent(self)) + # XXX: This should be renamed to replace_with_uuids def replace_with_objects(self, uids): if not self.isAllowedToEdit(): raise Unauthorized( From a1753b17d88bac593a994f3dd7d481ce6299fce6 Mon Sep 17 00:00:00 2001 From: Franco Pellegrini Date: Fri, 26 Sep 2014 15:32:09 -0300 Subject: [PATCH 06/63] Left note in file for testing custom URL functionality once tests are fixed --- src/collective/cover/tests/test_carousel_tile.robot | 2 ++ src/collective/cover/widgets/textlines_sortable_input.pt | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/collective/cover/tests/test_carousel_tile.robot b/src/collective/cover/tests/test_carousel_tile.robot index d719fcdaf..79ca20510 100644 --- a/src/collective/cover/tests/test_carousel_tile.robot +++ b/src/collective/cover/tests/test_carousel_tile.robot @@ -105,6 +105,8 @@ Test Carousel Tile Compose Cover Page Should Contain Galleria.configure({ autoplay: false }); + # Need to add functional test for the custom URL here + # delete the tile Edit Cover Layout Delete Tile diff --git a/src/collective/cover/widgets/textlines_sortable_input.pt b/src/collective/cover/widgets/textlines_sortable_input.pt index dd8a0be51..90686d89c 100644 --- a/src/collective/cover/widgets/textlines_sortable_input.pt +++ b/src/collective/cover/widgets/textlines_sortable_input.pt @@ -63,7 +63,8 @@

Custom URL - From a51841eb156f4ab7de1c5edf695260c61a8c7e38 Mon Sep 17 00:00:00 2001 From: Franco Pellegrini Date: Fri, 26 Sep 2014 15:56:47 -0300 Subject: [PATCH 07/63] Update changelog and include upgrade step --- CHANGES.rst | 7 +++ src/collective/cover/profiles.zcml | 13 +++++ .../cover/profiles/default/metadata.xml | 2 +- src/collective/cover/upgrades.py | 49 +++++++++++++++++++ 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 49f3e055a..c1e599579 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,12 @@ There's a frood who really knows where his towel is. 1.0a10 (unreleased) ^^^^^^^^^^^^^^^^^^^ +- Allow to set a custom URL on elements for a Carousel (solves `#377`_). + [frapell] + +- If an Image content is included, redirect to view and not the image itself + [frapell] + - Remove PloneFormGen's 'Form Folder' from default searchable types (fixes `#438`_). [djowett] @@ -538,6 +544,7 @@ There's a frood who really knows where his towel is. .. _`#351`: https://github.com/collective/collective.cover/issues/351 .. _`#371`: https://github.com/collective/collective.cover/issues/371 .. _`#374`: https://github.com/collective/collective.cover/issues/374 +.. _`#377`: https://github.com/collective/collective.cover/issues/377 .. _`#381`: https://github.com/collective/collective.cover/issues/381 .. _`#383`: https://github.com/collective/collective.cover/issues/383 .. _`#393`: https://github.com/collective/collective.cover/issues/393 diff --git a/src/collective/cover/profiles.zcml b/src/collective/cover/profiles.zcml index 185ac84e4..43ddc7104 100644 --- a/src/collective/cover/profiles.zcml +++ b/src/collective/cover/profiles.zcml @@ -111,4 +111,17 @@ /> + + + + + + diff --git a/src/collective/cover/profiles/default/metadata.xml b/src/collective/cover/profiles/default/metadata.xml index 2cfcc516e..07eb4b5a3 100644 --- a/src/collective/cover/profiles/default/metadata.xml +++ b/src/collective/cover/profiles/default/metadata.xml @@ -1,6 +1,6 @@ - 9 + 10 profile-collective.js.galleria:default profile-collective.js.jqueryui:default diff --git a/src/collective/cover/upgrades.py b/src/collective/cover/upgrades.py index a89a7f015..aaca93d0c 100644 --- a/src/collective/cover/upgrades.py +++ b/src/collective/cover/upgrades.py @@ -2,8 +2,10 @@ from collective.cover.config import PROJECTNAME from collective.cover.controlpanel import ICoverSettings +from persistent.mapping import PersistentMapping from plone import api from plone.registry.interfaces import IRegistry +from plone.tiles.interfaces import ITileDataManager from zope.component import getUtility import logging @@ -117,3 +119,50 @@ def change_configlet_permissions(context): configlet = cptool.getActionObject('Products/cover') configlet.permissions = ('collective.cover: Setup',) logger.info('configlet permissions updated') + + +def upgrade_carousel_tiles_custom_url(context): + # Get covers + covers = context.portal_catalog(portal_type='collective.cover.content') + logger.info('About to update %s covers' % len(covers)) + for cover in covers: + obj = cover.getObject() + tile_ids = obj.list_tiles(types=[ + u'collective.cover.carousel', + u'collective.cover.list' + ]) + for tile_id in tile_ids: + tile = obj.get_tile(tile_id) + old_data = ITileDataManager(tile).get() + uuids = old_data['uuids'] + if isinstance(uuids, PersistentMapping): + # This tile is fixed, carry on + logger.info( + 'Tile %s at %s was already updated' % + (tile_id, cover.getPath()) + ) + continue + if not uuids: + # This tile did not have data, so ignore + logger.info( + 'Tile %s at %s did not have any data' % + (tile_id, cover.getPath()) + ) + continue + + new_data = PersistentMapping() + order = 0 + for uuid in uuids: + if uuid not in new_data.keys(): + entry = PersistentMapping() + entry[u'order'] = unicode(order) + new_data[uuid] = entry + order += 1 + + old_data['uuids'] = new_data + ITileDataManager(tile).set(old_data) + + logger.info( + 'Tile %s at %s updated' % (tile_id, cover.getPath()) + ) + logger.info('Done') From 0e4afaf3fa331f1a72b93ddfa99f2f64997fd0cc Mon Sep 17 00:00:00 2001 From: Franco Pellegrini Date: Fri, 26 Sep 2014 18:43:10 -0300 Subject: [PATCH 08/63] Fix tests and add an upgrade test --- src/collective/cover/tests/test_upgrades.py | 57 +++++++++++++++++++-- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/src/collective/cover/tests/test_upgrades.py b/src/collective/cover/tests/test_upgrades.py index 43681e1af..4976d07c8 100644 --- a/src/collective/cover/tests/test_upgrades.py +++ b/src/collective/cover/tests/test_upgrades.py @@ -2,8 +2,10 @@ from collective.cover.config import DEFAULT_GRID_SYSTEM from collective.cover.testing import INTEGRATION_TESTING +from persistent.mapping import PersistentMapping from plone import api from plone.registry.interfaces import IRegistry +from plone.tiles.interfaces import ITileDataManager from zope.component import getUtility import unittest @@ -56,7 +58,7 @@ def setUp(self): def test_upgrade_to_6_registrations(self): version = self.setup.getLastVersionForProfile(self.profile_id)[0] - self.assertTrue(version >= self.to_version) + self.assertTrue(int(version) >= int(self.to_version)) self.assertEqual(self._how_many_upgrades_to_do(), 2) def test_issue_201(self): @@ -130,7 +132,7 @@ def setUp(self): def test_upgrade_to_7_registrations(self): version = self.setup.getLastVersionForProfile(self.profile_id)[0] - self.assertTrue(version >= self.to_version) + self.assertTrue(int(version) >= int(self.to_version)) self.assertEqual(self._how_many_upgrades_to_do(), 3) def test_issue_330(self): @@ -182,7 +184,7 @@ def setUp(self): def test_upgrade_to_8_registrations(self): version = self.setup.getLastVersionForProfile(self.profile_id)[0] - self.assertTrue(version >= self.to_version) + self.assertTrue(int(version) >= int(self.to_version)) self.assertEqual(self._how_many_upgrades_to_do(), 1) def test_issue_371(self): @@ -198,7 +200,7 @@ def setUp(self): def test_upgrade_to_9_registrations(self): version = self.setup.getLastVersionForProfile(self.profile_id)[0] - self.assertTrue(version >= self.to_version) + self.assertTrue(int(version) >= int(self.to_version)) self.assertEqual(self._how_many_upgrades_to_do(), 3) def test_issue_423(self): @@ -215,3 +217,50 @@ def test_issue_423(self): self._do_upgrade_step(step) permissions = configlet.permissions self.assertEqual(permissions, ('collective.cover: Setup',)) + + +class Upgrade9to10TestCase(UpgradeTestCaseBase): + + def setUp(self): + UpgradeTestCaseBase.setUp(self, u'9', u'10') + + def test_upgrade_to_10_registrations(self): + version = self.setup.getLastVersionForProfile(self.profile_id)[0] + self.assertTrue(int(version) >= int(self.to_version)) + self.assertEqual(self._how_many_upgrades_to_do(), 1) + + def test_new_uuids_structure(self): + title = u'Upgrade carousel tiles' + step = self._get_upgrade_step(title) + self.assertIsNotNone(step) + + # simulate state on previous version + with api.env.adopt_roles(['Manager']): + api.content.create( + self.portal, 'collective.cover.content', + 'test-cover', + template_layout='Empty layout', + ) + + cover = self.portal['test-cover'] + + cover.cover_layout = ( + '[{"type": "row", "children": [{"data": {"layout-type": "column", ' + '"column-size": 16}, "type": "group", "children": [{"tile-type": ' + '"collective.cover.carousel", "type": "tile", "id": ' + '"ca6ba6675ef145e4a569c5e410af7511"}], "roles": ["Manager"]}]}]' + ) + + tile = cover.get_tile('ca6ba6675ef145e4a569c5e410af7511') + old_data = ITileDataManager(tile).get() + old_data['uuids'] = ['uuid1', 'uuid3', 'uuid2'] + ITileDataManager(tile).set(old_data) + + # run the upgrade step to validate the update + self._do_upgrade_step(step) + old_data = ITileDataManager(tile).get() + self.assertFalse(isinstance(old_data['uuids'], list)) + self.assertTrue(isinstance(old_data['uuids'], PersistentMapping)) + self.assertEqual(old_data['uuids']['uuid1']['order'], u'0') + self.assertEqual(old_data['uuids']['uuid2']['order'], u'2') + self.assertEqual(old_data['uuids']['uuid3']['order'], u'1') From 6fdbc4bd60aed9c7cf508c8027403620be8489f6 Mon Sep 17 00:00:00 2001 From: Franco Pellegrini Date: Fri, 26 Sep 2014 19:51:43 -0300 Subject: [PATCH 09/63] We don't really need to inherit from DictMultiConverter, so we don't need to upgrade to newer z3c.form package --- src/collective/cover/tiles/carousel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/collective/cover/tiles/carousel.py b/src/collective/cover/tiles/carousel.py index 10cfd2012..39463b25e 100644 --- a/src/collective/cover/tiles/carousel.py +++ b/src/collective/cover/tiles/carousel.py @@ -10,7 +10,7 @@ from plone.tiles.interfaces import ITileDataManager from Products.CMFCore.utils import getToolByName from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile -from z3c.form.converter import DictMultiConverter +from z3c.form.converter import BaseDataConverter from zope import schema from zope.component import adapts from zope.interface import implements @@ -86,7 +86,7 @@ def init_js(self): return INIT_JS.format(self.id, str(self.autoplay()).lower()) -class UUIDSFieldDataConverter(DictMultiConverter): +class UUIDSFieldDataConverter(BaseDataConverter): """A data converter using the field's ``fromUnicode()`` method.""" adapts(IDict, ITextLinesSortableWidget) From 3a2235001b6ba0f296390980653a972209700cb3 Mon Sep 17 00:00:00 2001 From: Franco Pellegrini Date: Sat, 27 Sep 2014 02:17:51 -0300 Subject: [PATCH 10/63] Add docstrings to methods and also more tests --- .../cover/tests/test_carousel_tile.py | 53 ++++++++++++ .../tests/test_textlinessortable_widget.py | 86 +++++++++++++++++++ src/collective/cover/tiles/carousel.py | 29 ++++++- src/collective/cover/tiles/list.py | 25 +++++- .../cover/widgets/textlinessortable.py | 20 +++++ 5 files changed, 207 insertions(+), 6 deletions(-) create mode 100644 src/collective/cover/tests/test_textlinessortable_widget.py diff --git a/src/collective/cover/tests/test_carousel_tile.py b/src/collective/cover/tests/test_carousel_tile.py index 5f6b00b41..282091e90 100644 --- a/src/collective/cover/tests/test_carousel_tile.py +++ b/src/collective/cover/tests/test_carousel_tile.py @@ -3,6 +3,8 @@ from collective.cover.tests.base import TestTileMixin from collective.cover.tiles.carousel import CarouselTile from collective.cover.tiles.carousel import ICarouselTile +from collective.cover.tiles.carousel import UUIDSFieldDataConverter +from collective.cover.widgets.textlinessortable import TextLinesSortableWidget from persistent.mapping import PersistentMapping from plone.tiles.interfaces import ITileDataManager from plone.uuid.interfaces import IUUID @@ -99,3 +101,54 @@ def test_internal_structure(self): self.assertTrue(len(uuids) == 2) self.assertTrue(uuids[obj1.UID()]['order'] == u'0') self.assertTrue(uuids[obj2.UID()]['order'] == u'1') + + def test_custom_url(self): + # we start with an empty tile + self.assertTrue(self.tile.is_empty()) + uuids = ITileDataManager(self.tile).get().get('uuids', None) + + self.assertIsNone(uuids) + + # now we 3 elements + obj1 = self.portal['my-document'] + obj2 = self.portal['my-image'] + obj3 = self.portal['my-image1'] + + self.tile.populate_with_uids([ + obj1.UID(), obj2.UID(), obj3.UID() + ]) + + # For obj2 we will assign a custom_url + + uuids = ITileDataManager(self.tile).get().get('uuids', None) + uuids[obj2.UID()]['custom_url'] = u'http://www.custom_url.com' + + url1 = self.tile.get_url(obj1) + url2 = self.tile.get_url(obj2) + url3 = self.tile.get_url(obj3) + + # Document object should be the same as absolute_url + self.assertEqual(url1, obj1.absolute_url()) + # First image should return the custom URL + self.assertEqual(url2, u'http://www.custom_url.com') + # And second image should have the absolute_url and /view + self.assertEqual(url3, u'%s/view' % obj3.absolute_url()) + + def test_data_converter(self): + field = ICarouselTile['uuids'] + widget = TextLinesSortableWidget(self.request) + conv = UUIDSFieldDataConverter(field, widget) + + value = { + u'uuid1': {u'order': u'0'}, + u'uuid2': {u'order': u'2'}, + u'uuid3': {u'order': u'1'}, + } + + to_widget = conv.toWidgetValue(value) + self.assertEqual(to_widget, u'uuid1\r\nuuid3\r\nuuid2') + + to_field = conv.toFieldValue(value) + + self.assertEqual(to_field, value) + self.assertEqual(conv.toFieldValue({}), conv.field.missing_value) diff --git a/src/collective/cover/tests/test_textlinessortable_widget.py b/src/collective/cover/tests/test_textlinessortable_widget.py new file mode 100644 index 000000000..c5644d061 --- /dev/null +++ b/src/collective/cover/tests/test_textlinessortable_widget.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- + +from collective.cover.testing import INTEGRATION_TESTING +from collective.cover.widgets.textlinessortable import TextLinesSortableWidget + +import unittest + + +class TestTextLinesSortableWidget(unittest.TestCase): + layer = INTEGRATION_TESTING + + def setUp(self): + self.portal = self.layer['portal'] + self.request = self.layer['request'] + + def test_sort_results(self): + widget = TextLinesSortableWidget(self.request) + + obj1 = self.portal['my-image'] + obj2 = self.portal['my-image1'] + obj3 = self.portal['my-image2'] + + widget.context = {'uuids': { + obj1.UID(): {u'order': u'0'}, + obj2.UID(): {u'order': u'2'}, + obj3.UID(): {u'order': u'1'}, + } + } + + expected = [ + {'obj': obj1, 'uuid': obj1.UID()}, + {'obj': obj3, 'uuid': obj3.UID()}, + {'obj': obj2, 'uuid': obj2.UID()}, + ] + self.assertListEqual(widget.sort_results(), expected) + + widget.context = {'uuids': {}} + + expected = [] + self.assertListEqual(widget.sort_results(), expected) + + def test_thumbnail(self): + widget = TextLinesSortableWidget(self.request) + + obj1 = self.portal['my-image'] + obj2 = self.portal['my-document'] + + self.assertIsNotNone(widget.thumbnail(obj1)) + self.assertIsNone(widget.thumbnail(obj2)) + + def test_get_custom_url(self): + widget = TextLinesSortableWidget(self.request) + + obj1 = self.portal['my-image'] + obj2 = self.portal['my-image1'] + obj3 = self.portal['my-image2'] + + widget.context = {'uuids': { + obj1.UID(): {u'order': u'0', u'custom_url': u'custom_url'}, + obj2.UID(): {u'order': u'1', u'custom_url': u''}, + obj3.UID(): {u'order': u'2'}, + } + } + + self.assertEqual(widget.get_custom_url(obj1.UID()), u'custom_url') + self.assertEqual(widget.get_custom_url(obj2.UID()), u'') + self.assertEqual(widget.get_custom_url(obj3.UID()), u'') + + def test_extract(self): + name = 'uuid.field' + self.request.set(name, u'uuid1\r\nuuid3\r\nuuid2') + self.request.set(u'%s.custom_url.uuid1' % name, u'custom_url') + self.request.set(u'%s.custom_url.uuid2' % name, u'') + + widget = TextLinesSortableWidget(self.request) + widget.name = name + + expected = { + u'uuid1': {u'order': u'0', u'custom_url': u'custom_url'}, + u'uuid3': {u'order': u'1', u'custom_url': u''}, + u'uuid2': {u'order': u'2', u'custom_url': u''}, + } + + extracted_value = widget.extract() + + self.assertDictEqual(extracted_value, expected) diff --git a/src/collective/cover/tiles/carousel.py b/src/collective/cover/tiles/carousel.py index 39463b25e..07dd1392c 100644 --- a/src/collective/cover/tiles/carousel.py +++ b/src/collective/cover/tiles/carousel.py @@ -6,9 +6,9 @@ from collective.cover.tiles.list import ListTile from collective.cover.widgets.interfaces import ITextLinesSortableWidget from collective.cover.widgets.textlinessortable import TextLinesSortableFieldWidget +from plone import api from plone.autoform import directives as form from plone.tiles.interfaces import ITileDataManager -from Products.CMFCore.utils import getToolByName from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile from z3c.form.converter import BaseDataConverter from zope import schema @@ -61,9 +61,17 @@ def autoplay(self): return self.data['autoplay'] def get_url(self, item): - portal_properties = getToolByName(self.context, 'portal_properties') + """ Gets the URL for the item. It will return the "Custom URL", if + it was set, if not, the URL for the item will be returned + + :param item: [required] The item for which we want the URL to + :type item: Content object + :returns: URL for the item + """ + portal_properties = api.portal.get_tool(name='portal_properties') use_view_action = portal_properties.site_properties.getProperty( 'typesUseViewActionInListings', ()) + # First we get the url for the item itself url = item.absolute_url() if item.portal_type in use_view_action: url = url + '/view' @@ -73,6 +81,7 @@ def get_url(self, item): uuids = data['uuids'] if uuid in uuids: if uuids[uuid].get('custom_url', u""): + # If we had a custom url set, then get that url = uuids[uuid].get('custom_url') return url @@ -91,13 +100,25 @@ class UUIDSFieldDataConverter(BaseDataConverter): adapts(IDict, ITextLinesSortableWidget) def toWidgetValue(self, value): - """Just dispatch it.""" + """ Converts the internal stored value into something that a z3c.form + widget understands + + :param value: [required] The internally stored value + :type value: Dict + :returns: A string with UUIDs separated by \r\n + """ ordered_uuids = [(k, v) for k, v in value.items()] ordered_uuids.sort(key=lambda x: x[1]['order']) return '\r\n'.join([i[0] for i in ordered_uuids]) def toFieldValue(self, value): - """Just dispatch it.""" + """ Passes the value extracted from the widget to the internal + structure. In this case, the value expected is already formatted + + :param value: [required] The data extracted from the widget + :type value: Dict + :returns: The value to be stored in the tile + """ if not len(value) or not isinstance(value, dict): return self.field.missing_value return value diff --git a/src/collective/cover/tiles/list.py b/src/collective/cover/tiles/list.py index 009a41e31..af6f1e830 100644 --- a/src/collective/cover/tiles/list.py +++ b/src/collective/cover/tiles/list.py @@ -151,12 +151,23 @@ def set_limit(self): self.limit = int(field.get('size', self.limit)) def populate_with_object(self, obj): + """ Add an object to the list of items + + :param obj: [required] The object to be added + :type obj: Content object + """ super(ListTile, self).populate_with_object(obj) # check permission uids = ICoverUIDsProvider(obj).getUIDs() if uids: self.populate_with_uids(uids) def populate_with_uids(self, uuids): + """ Add a list of elements to the list of items. This method will + append new elements to the already existing list of items + + :param uuids: The list of objects' UUIDs to be used + :type uuids: List of strings + """ if not self.isAllowedToEdit(): raise Unauthorized( _('You are not allowed to add content to this tile')) @@ -199,7 +210,12 @@ def populate_with_uids(self, uuids): notify(ObjectModifiedEvent(self)) # XXX: This should be renamed to replace_with_uuids - def replace_with_objects(self, uids): + def replace_with_objects(self, uuids): + """ Replaces the whole list of items with a new list of items + + :param uuids: The list of objects' UUIDs to be used + :type uuids: List of strings + """ if not self.isAllowedToEdit(): raise Unauthorized( _('You are not allowed to add content to this tile')) @@ -209,9 +225,14 @@ def replace_with_objects(self, uids): old_data['uuids'] = PersistentMapping() data_mgr.set(old_data) # Repopulate with clean list - self.populate_with_uids(uids) + self.populate_with_uids(uuids) def remove_item(self, uid): + """ Removes an item from the list + + :param uid: [required] uid for the object that wants to be removed + :type uid: string + """ super(ListTile, self).remove_item(uid) # check permission data_mgr = ITileDataManager(self) old_data = data_mgr.get() diff --git a/src/collective/cover/widgets/textlinessortable.py b/src/collective/cover/widgets/textlinessortable.py index cdcf6753a..0396e9ee1 100644 --- a/src/collective/cover/widgets/textlinessortable.py +++ b/src/collective/cover/widgets/textlinessortable.py @@ -29,6 +29,10 @@ def render(self): return self.configure_template(self) def sort_results(self): + """ Returns a sorted list of the stored objects + + :returns: A sorted list of objects + """ uuids = self.context['uuids'] if uuids: ordered_uuids = [(k, v) for k, v in uuids.items()] @@ -39,6 +43,12 @@ def sort_results(self): return [] def thumbnail(self, item): + """ Returns the 'tile' scale for the image added to the item + + :param item: [required] The object to take the image from + :type item: Content object + :returns: The tag for the scale + """ scales = item.restrictedTraverse('@@images') try: return scales.scale('image', 'tile') @@ -46,6 +56,12 @@ def thumbnail(self, item): return None def get_custom_url(self, uuid): + """ Returns the custom URL assigned to a specific item + + :param uuid: [required] The object's UUID + :type uuid: string + :returns: The custom URL + """ url = u'' uuids = self.context['uuids'] if uuid in uuids: @@ -54,6 +70,10 @@ def get_custom_url(self, uuid): return url def extract(self): + """ Extracts the data from the HTML form and returns it + + :returns: A dictionary with the information + """ values = self.request.get(self.name).split('\r\n') uuids = [i for i in values if i] results = dict() From 2c0ad63516efaf7c038e3c1870797aee7e701c74 Mon Sep 17 00:00:00 2001 From: Franco Pellegrini Date: Wed, 1 Oct 2014 20:17:00 -0300 Subject: [PATCH 11/63] Reindex created objects --- src/collective/cover/testing.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/collective/cover/testing.py b/src/collective/cover/testing.py index 98761285c..813d3815b 100644 --- a/src/collective/cover/testing.py +++ b/src/collective/cover/testing.py @@ -133,11 +133,15 @@ def setUpPloneSite(self, portal): self.applyProfile(portal, 'collective.cover:default') self.applyProfile(portal, 'collective.cover:testfixture') portal['my-image'].setImage(generate_jpeg(50, 50)) + portal['my-image'].reindexObject() portal['my-image1'].setImage(generate_jpeg(50, 50)) + portal['my-image1'].reindexObject() portal['my-image2'].setImage(generate_jpeg(50, 50)) + portal['my-image2'].reindexObject() portal['my-file'].setFile(loadFile('lorem_ipsum.txt')) portal['my-file'].reindexObject() portal['my-news-item'].setImage(generate_jpeg(50, 50)) + portal['my-news-item'].reindexObject() portal_workflow = portal.portal_workflow portal_workflow.setChainForPortalTypes( ['Collection', 'Event'], ['simple_publication_workflow']) From a4ca1c77d912e7149efef18f58a2ac8273a2414f Mon Sep 17 00:00:00 2001 From: Franco Pellegrini Date: Wed, 1 Oct 2014 20:18:00 -0300 Subject: [PATCH 12/63] Bring back functionality of not including objects with no images. 8df37e04d7299a0cb1a90e9f0a8ace746859c49c --- src/collective/cover/tiles/carousel.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/collective/cover/tiles/carousel.py b/src/collective/cover/tiles/carousel.py index 07dd1392c..f9b60a7e8 100644 --- a/src/collective/cover/tiles/carousel.py +++ b/src/collective/cover/tiles/carousel.py @@ -54,6 +54,23 @@ class CarouselTile(ListTile): is_editable = True short_name = _(u'msg_short_name_carousel', default=u'Carousel') + def populate_with_object(self, obj): + """ Add a list of elements to the list of items. This method will + append new elements to the already existing list of items. + If the object doesn't have an image associated, it will not be + included + + :param uuids: The list of objects' UUIDs to be used + :type uuids: List of strings + """ + try: + image_size = obj.restrictedTraverse('@@images').getImageSize() + except: + image_size = None + if not image_size: + return + super(CarouselTile, self).populate_with_object(obj) + def autoplay(self): if self.data['autoplay'] is None: return True # default value From 5eb65ec4e3c24aca96c772bb8e07f819db32a9e6 Mon Sep 17 00:00:00 2001 From: Franco Pellegrini Date: Wed, 1 Oct 2014 20:18:40 -0300 Subject: [PATCH 13/63] Fix tests related to Carousel and add new ones for testing Custom URL functionality --- .../cover/tests/test_carousel_tile.py | 6 ++- .../cover/tests/test_carousel_tile.robot | 54 ++++++++++++++----- 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/collective/cover/tests/test_carousel_tile.py b/src/collective/cover/tests/test_carousel_tile.py index 282091e90..a51150f31 100644 --- a/src/collective/cover/tests/test_carousel_tile.py +++ b/src/collective/cover/tests/test_carousel_tile.py @@ -51,8 +51,10 @@ def test_crud(self): # tile's data attribute is cached; reinstantiate it self.tile = self.cover.restrictedTraverse( '@@{0}/{1}'.format('collective.cover.carousel', 'test')) - self.assertEqual(len(self.tile.results()), 2) - self.assertIn(obj1, self.tile.results()) + + # Document should not have been added + self.assertEqual(len(self.tile.results()), 1) + self.assertNotIn(obj1, self.tile.results()) self.assertIn(obj2, self.tile.results()) # next, we replace the list of objects with a different one diff --git a/src/collective/cover/tests/test_carousel_tile.robot b/src/collective/cover/tests/test_carousel_tile.robot index 0f3d7bd25..29e568474 100644 --- a/src/collective/cover/tests/test_carousel_tile.robot +++ b/src/collective/cover/tests/test_carousel_tile.robot @@ -10,8 +10,10 @@ Suite Teardown Close all browsers ${carousel_tile_location} "collective.cover.carousel" ${document_selector} //div[@id="content-trees"]//li[@class="ui-draggable"]/a[@data-ct-type="Document"]/span[text()='My document']/.. -${image_selector} //div[@id="content-trees"]//li[@class="ui-draggable"]/a[@data-ct-type="Image"]/span[text()='my-image']/.. -${image_selector2} //div[@id="content-trees"]//li[@class="ui-draggable"]/a[@data-ct-type="Image"]/span[text()='my-image1']/.. +${image_selector} //div[@id="content-trees"]//li[@class="ui-draggable"]/a[@data-ct-type="Image"]/span[text()='Test image']/.. +${image_title} //div[@class="galleria-info-title"]/a[text()='Test image']/.. +${image_selector2} //div[@id="content-trees"]//li[@class="ui-draggable"]/a[@data-ct-type="Image"]/span[text()='Test image #1']/.. +${image_title2} //div[@class="galleria-info-title"]/a[text()='Test image #1']/.. ${tile_selector} div.tile-container div.tile ${autoplay_id} collective-cover-carousel-autoplay-0 ${edit_link_selector} a.edit-tile-link @@ -21,13 +23,13 @@ ${edit_link_selector} a.edit-tile-link Get Total Carousel Images [Documentation] Total number of images in carousel is stored in this ... element - ${return} = Get Matching XPath Count //div[@class="galleria-stage"]//div[@class="galleria-image"]/img + ${return} = Get Element Attribute xpath=//div[@class="galleria-stage"]/div[@class="galleria-counter"]/span[@class="galleria-total"]@innerHTML [Return] ${return} *** Test cases *** Test Carousel Tile - [Tags] Expected Failure + # [Tags] Expected Failure Enable Autologin as Site Administrator Go to Homepage @@ -52,7 +54,8 @@ Test Carousel Tile # move to the default view and check tile persisted Click Link link=View - Wait Until Page Contains Test image + # Wait Until Page Contains would always work because of the top navigation + Wait Until Page Contains Element xpath=${image_title} Page Should Contain This image was created for testing purposes # we have 1 image in the carousel ${images} = Get Total Carousel Images @@ -63,14 +66,14 @@ Test Carousel Tile Sleep 1s Wait for carousel to load Open Content Chooser Click Element link=Content tree - Drag And Drop xpath=${image_selector2} css=${tile_selector} + # Need to change view before second image is loaded # move to the default view and check tile persisted Click Link link=View - Sleep 5s Wait for carousel to load - Wait Until Page Contains Test image #1 + # Wait Until Page Contains would always work because of the top navigation + Wait Until Page Contains Element xpath=${image_title2} Page Should Contain This image #1 was created for testing purposes # we now have 2 images in the carousel ${images} = Get Total Carousel Images @@ -84,11 +87,10 @@ Test Carousel Tile Drag And Drop xpath=${document_selector} css=${tile_selector} - # No point to test Documents - they are not used in carousel - # see: https://github.com/collective/collective.cover/commit/8df37e04d7299a0cb1a90e9f0a8ace746859c49c Click Link link=View - #Wait Until Page Contains My document - #Page Should Contain This document was created for testing purposes + # We should still have 2 images in the carousel + ${images} = Get Total Carousel Images + Should Be Equal '${images}' '2' # carousel autoplay is enabled Page Should Contain options.autoplay = true; @@ -107,7 +109,33 @@ Test Carousel Tile Compose Cover Page Should Contain options.autoplay = false; - # Need to add functional test for the custom URL here + # Test Custom URL functionality + Click Link link=View + ${image_url} = Get Element Attribute xpath=.//div[@class='galleria-info-title']/a@href + Should Be Equal ${image_url} ${PLONE_URL}/my-image/view + + # Go to the right + Click Element xpath=.//div[@class='galleria-image-nav-right'] + Wait Until Page Contains Element xpath=${image_title2} + ${image_url} = Get Element Attribute xpath=.//div[@class='galleria-info-title']/a@href + Should Be Equal ${image_url} ${PLONE_URL}/my-image1/view + + + Compose Cover + Click Link css=${edit_link_selector} + Input Text xpath=.//div[@class='textline-sortable-element'][2]/input http://www.google.com + Click Button Save + Sleep 2s Wait for carousel to load + + Click Link link=View + ${image_url} = Get Element Attribute xpath=.//div[@class='galleria-info-title']/a@href + Should Be Equal ${image_url} ${PLONE_URL}/my-image/view + + # Go to the right + Click Element xpath=.//div[@class='galleria-image-nav-right'] + Wait Until Page Contains Element xpath=${image_title2} + ${image_url} = Get Element Attribute xpath=.//div[@class='galleria-info-title']/a@href + Should Be Equal ${image_url} http://www.google.com/ # delete the tile Edit Cover Layout From b10b1ba15e22873c731efd41b76102f3630314bb Mon Sep 17 00:00:00 2001 From: Franco Pellegrini Date: Fri, 3 Oct 2014 13:38:36 -0300 Subject: [PATCH 14/63] Add the Expected Failure back because Travis is still randomly failing --- src/collective/cover/tests/test_carousel_tile.robot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/collective/cover/tests/test_carousel_tile.robot b/src/collective/cover/tests/test_carousel_tile.robot index 29e568474..31cf68add 100644 --- a/src/collective/cover/tests/test_carousel_tile.robot +++ b/src/collective/cover/tests/test_carousel_tile.robot @@ -29,7 +29,7 @@ Get Total Carousel Images *** Test cases *** Test Carousel Tile - # [Tags] Expected Failure + [Tags] Expected Failure Enable Autologin as Site Administrator Go to Homepage From f673e701ceff6a57da1d0324ce3b7b2698091064 Mon Sep 17 00:00:00 2001 From: hvelarde Date: Mon, 20 Oct 2014 12:45:13 -0200 Subject: [PATCH 15/63] Update package documentation --- CHANGES.rst | 5 +++++ README.rst | 20 ++++++++------------ docs/end-user.rst | 5 +++++ 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index f7d8ec215..8ca11cb17 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -9,6 +9,10 @@ There's a frood who really knows where his towel is. - Add an option to extend JS configuration to reload tile in custom tiles (outside of collective.cover). [rodfersou] +- Include a behavior to add a couple of fields that enable reloading the current page after a certain amount of time. + Typical use case is a news portal that wants to keep the front page updated and increase the number of page views. + [hvelarde] + - Fix modification time disappearing from context data and handling missing value (fixes `#449`_). [mhora] @@ -559,5 +563,6 @@ There's a frood who really knows where his towel is. .. _`#421`: https://github.com/collective/collective.cover/issues/421 .. _`#423`: https://github.com/collective/collective.cover/issues/423 .. _`#426`: https://github.com/collective/collective.cover/issues/426 +.. _`#438`: https://github.com/collective/collective.cover/issues/438 .. _`#449`: https://github.com/collective/collective.cover/issues/449 .. _`PloneFormGen`: https://pypi.python.org/pypi/Products.PloneFormGen diff --git a/README.rst b/README.rst index 69030abb8..f189a9cd9 100644 --- a/README.rst +++ b/README.rst @@ -14,12 +14,7 @@ offer. However, despite offering rich resources to build a cover, ``collective.cover`` also provides a very easy mechanism for managing its contents, built around a drag-and-drop interface. -``collective.cover`` is based on `Blocks`_ and `Tiles`_, like `Deco`_, the new -layout composition system for Plone. - -.. TODO: explain why we need cover instead of just using Deco itself. - -.. TODO: add a comparison between Deco and collective.cover +``collective.cover`` is based on `Blocks`_ and `Tiles`_, like `Deco`_, the layout composition system proposed for Plone. .. _`Blocks`: https://github.com/plone/plone.app.blocks .. _`Deco`: https://github.com/plone/plone.app.deco @@ -28,17 +23,19 @@ layout composition system for Plone. Demo ^^^^ -For impatient types, here is a demo installation of collective.cover: http://collective-cover.herokuapp.com. +For impatient types, there is a demo installation of collective.cover on `Heroku`_. It needs about 60 seconds to spin up and it will purge all changes after about an hour of non-usage. +.. _`Heroku`: http://collective-cover.herokuapp.com + Use cases ^^^^^^^^^ -Suppose you are running The Planet, a news site that has a bunch of editors +Suppose you are running The Planet, a news portal that has a bunch of editors focused on getting news on different topics, like Economy, Health or Sports. If you are the main publisher of the site, you may want to delegate the -construction of the cover page of the Economy section to the people working on +construction of the front page of the Economy section to the people working on that content area, but you might not want them messing around the Sports section as well. @@ -83,8 +80,8 @@ Mostly Harmless :target: https://coveralls.io/r/collective/collective.cover .. image:: https://pypip.in/d/collective.cover/badge.png - :target: https://pypi.python.org/pypi/collective.cover/ :alt: Downloads + :target: https://pypi.python.org/pypi/collective.cover/ Got an idea? Found a bug? Let us know by `opening a support ticket`_. @@ -114,8 +111,7 @@ See the `complete list of bugs on GitHub`_. Don't Panic ----------- -We are currently working on the documentation of the package; this is what we -have right now (contributions are always welcomed): +We are currently working on the documentation of the package; this is what we have right now (contributions are always welcomed): * `Quick Tour video on YouTube`_. * `End user documentation`_ diff --git a/docs/end-user.rst b/docs/end-user.rst index 3fd5bab48..45e392a22 100644 --- a/docs/end-user.rst +++ b/docs/end-user.rst @@ -252,6 +252,11 @@ The changes will be applied to your cover immediately. Behaviors ^^^^^^^^^ +To enable behaviors go to 'Site Setup' and select 'Dexterity content types'. +Look for 'Cover' content type, select it and then select 'Behaviors'. + +The following behaviors are included in this package: + Refresh +++++++ From 1f4a7e90c381113886f76e29f68aa1fa2a528c22 Mon Sep 17 00:00:00 2001 From: hvelarde Date: Mon, 20 Oct 2014 12:56:27 -0200 Subject: [PATCH 16/63] Remove needless code --- src/collective/cover/tiles/edit_widgets/__init__.py | 1 - src/collective/cover/upgrades/__init__.py | 4 ---- 2 files changed, 5 deletions(-) diff --git a/src/collective/cover/tiles/edit_widgets/__init__.py b/src/collective/cover/tiles/edit_widgets/__init__.py index 40a96afc6..e69de29bb 100644 --- a/src/collective/cover/tiles/edit_widgets/__init__.py +++ b/src/collective/cover/tiles/edit_widgets/__init__.py @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/src/collective/cover/upgrades/__init__.py b/src/collective/cover/upgrades/__init__.py index c544b0fd1..8c9091717 100644 --- a/src/collective/cover/upgrades/__init__.py +++ b/src/collective/cover/upgrades/__init__.py @@ -24,8 +24,6 @@ def issue_201(context): logger.info('"{0}"" resource was removed'.format(old_id)) css_tool.cookResources() logger.info('CSS resources were cooked') - else: - logger.debug('"{0}" resource not found in portal_css'.format(old_id)) # now we mess with the JS registry js_tool = api.portal.get_tool('portal_javascripts') @@ -41,8 +39,6 @@ def issue_201(context): js_tool.cookResources() logger.info('JS resources were cooked') - else: - logger.debug('"{0}" resource not found in portal_javascripts'.format(old_id)) def issue_303(context): From bf386feef911e0456b86a14edc5dd58a3ee20fec Mon Sep 17 00:00:00 2001 From: Carlos de la Guardia Date: Thu, 30 Oct 2014 23:49:47 -0600 Subject: [PATCH 17/63] Modifying the old_annotations while iterating through them, caused their size to be reduced and the loop to end early, leaving annotations in the object --- src/collective/cover/events.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/collective/cover/events.py b/src/collective/cover/events.py index d7536726f..6d58b3bef 100644 --- a/src/collective/cover/events.py +++ b/src/collective/cover/events.py @@ -23,7 +23,8 @@ def override_object_annotations(cover, event): old_annotations = IAnnotations(event.baseline) new_annotations = IAnnotations(event.object) - for key in old_annotations: + old_keys = list(old_annotations.keys()) + for key in old_keys: # First remove all annotations in relation to tiles if key.startswith('plone.tiles.'): del old_annotations[key] From 680b54ffd01a42a6b8d460d999fabb778bdda66a Mon Sep 17 00:00:00 2001 From: hvelarde Date: Thu, 23 Oct 2014 16:20:14 -0200 Subject: [PATCH 18/63] Enable profiling information --- buildout.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/buildout.cfg b/buildout.cfg index 2f4cd515c..1e6d04efa 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -41,6 +41,8 @@ eggs = z3c.dependencychecker [instance] zcml += iw.debug +zope-conf-additional += publisher-profile-file ${buildout:directory}/var/instance/profile.dat +environment-vars += PROFILE_PUBLISHER 1 [i18ndude] recipe = zc.recipe.egg From 473c87d06fa0619fff8df0dfe71e4919baf50c38 Mon Sep 17 00:00:00 2001 From: hvelarde Date: Fri, 31 Oct 2014 14:52:01 -0200 Subject: [PATCH 19/63] Avoid issue with new releases of zc.buildout --- .travis.yml | 2 +- travis.cfg | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 737c30565..c66c922bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ install: - sed -ie "s#versions-4.x.cfg#versions-$PLONE_VERSION.x.cfg#" travis.cfg - test $JQUERY && sed -ie "s#plone.app.jquery = 1.7.2#plone.app.jquery = $JQUERY#" versions-4.3.x.cfg || true - mkdir -p buildout-cache/downloads - - python bootstrap.py -c travis.cfg + - python bootstrap.py -c travis.cfg -v 2.2.1 - bin/buildout -c travis.cfg annotate - bin/buildout -c travis.cfg -N -q before_script: diff --git a/travis.cfg b/travis.cfg index 6de42e71b..bb002fd56 100644 --- a/travis.cfg +++ b/travis.cfg @@ -21,3 +21,6 @@ jshint = True jshint-bin = bin/jshint debug-statements = True prefer-single-quotes = True + +[versions] +zc.buildout = 2.2.1 From e8c1b66e949b57296888ffb5b00e79ebdb2e353a Mon Sep 17 00:00:00 2001 From: hvelarde Date: Fri, 31 Oct 2014 15:34:53 -0200 Subject: [PATCH 20/63] Update changelog --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 95e64419d..8e36c6ada 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -12,6 +12,9 @@ There's a frood who really knows where his towel is. - If an Image content is included, redirect to view and not the image itself [frapell] +- Modifying annotations, while iterating through them, caused their size to be reduced and the loop to end early, leaving annotations in the object. + [cguardia] + - Add an option to extend JS configuration to reload tile in custom tiles (outside of collective.cover). [rodfersou] From e8c6eb87432723cf959ff8d738d0e44510921d3f Mon Sep 17 00:00:00 2001 From: hvelarde Date: Fri, 31 Oct 2014 16:33:57 -0200 Subject: [PATCH 21/63] Preparing release 1.0a10 --- CHANGES.rst | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 8e36c6ada..4a6cacf09 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,7 +3,7 @@ Changelog There's a frood who really knows where his towel is. -1.0a10 (unreleased) +1.0a10 (2014-10-31) ^^^^^^^^^^^^^^^^^^^ - Allow to set a custom URL on elements for a Carousel (solves `#377`_). diff --git a/setup.py b/setup.py index 21cf522f4..cf0ad9e65 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools import setup -version = '1.0a10.dev0' +version = '1.0a10' description = 'A sane, working, editor-friendly way of creating front pages and other composite pages. Working now, for mere mortals.' long_description = ( open('README.rst').read() + '\n' + From 61b5ab2bf47834312c79516b11568794e24e9e7b Mon Sep 17 00:00:00 2001 From: hvelarde Date: Fri, 31 Oct 2014 16:35:31 -0200 Subject: [PATCH 22/63] Back to development: 1.0a11 --- CHANGES.rst | 6 ++++++ setup.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 4a6cacf09..c36323b7e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,12 @@ Changelog There's a frood who really knows where his towel is. +1.0a11 (unreleased) +^^^^^^^^^^^^^^^^^^^ + +- Nothing changed yet. + + 1.0a10 (2014-10-31) ^^^^^^^^^^^^^^^^^^^ diff --git a/setup.py b/setup.py index cf0ad9e65..463c2eed7 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools import setup -version = '1.0a10' +version = '1.0a11.dev0' description = 'A sane, working, editor-friendly way of creating front pages and other composite pages. Working now, for mere mortals.' long_description = ( open('README.rst').read() + '\n' + From 5ea169c0ab68875031ed5565d55f48f47e7afdc4 Mon Sep 17 00:00:00 2001 From: TsungWei Hu Date: Sun, 2 Nov 2014 13:19:20 +0800 Subject: [PATCH 23/63] Update Traditional Chinese Translation. --- .../zh_TW/LC_MESSAGES/collective.cover.po | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/collective/cover/locales/zh_TW/LC_MESSAGES/collective.cover.po b/src/collective/cover/locales/zh_TW/LC_MESSAGES/collective.cover.po index c8782393f..6993dcbcd 100644 --- a/src/collective/cover/locales/zh_TW/LC_MESSAGES/collective.cover.po +++ b/src/collective/cover/locales/zh_TW/LC_MESSAGES/collective.cover.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: collective.cover\n" "POT-Creation-Date: 2014-08-22 20:31+0000\n" -"PO-Revision-Date: 2014-06-23 10:50+0800\n" +"PO-Revision-Date: 2014-11-02 13:10+0800\n" "Last-Translator: TsungWei Hu \n" "Language-Team: Plone I18N \n" "MIME-Version: 1.0\n" @@ -72,7 +72,7 @@ msgstr "" #: collective/cover/controlpanel.py:25 msgid "Available tiles" -msgstr "" +msgstr "圖磚選項" #: tiles/configure.zcml msgid "Banner Tile" @@ -102,7 +102,7 @@ msgstr "輪播磚" #: collective/cover/controlpanel.py:57 msgid "Choose a grid system" -msgstr "" +msgstr "網格設計的選項。" #: collective/cover/models/cover.xml msgid "Choose one of the predefined layouts" @@ -153,7 +153,7 @@ msgstr "日期" #: collective/cover/layout.py:240 msgid "Deco (16 columns, default)" -msgstr "" +msgstr "Deco (預設 16 欄)" #: collective/cover/tiles/basic.py:27 #: collective/cover/tiles/collection.py:39 @@ -192,7 +192,7 @@ msgstr "內嵌程式碼" #: collective/cover/controlpanel.py:47 msgid "Enter a list of styles to appear in the style pulldown. Format is title|className, one per line." -msgstr "" +msgstr "下拉選單裡的風格樣式,格式是 title|className 逐行輸入。" #: collective/cover/browser/templates/layoutedit.pt:79 msgid "Export layout" @@ -228,7 +228,7 @@ msgstr "" #: collective/cover/controlpanel.py:56 msgid "Grid System" -msgstr "" +msgstr "網格設計" #: collective/cover/tiles/configuration_widgets/textline.pt:63 msgid "HTML tag" @@ -303,7 +303,7 @@ msgstr "" #: collective/cover/controlpanel.py:35 msgid "Only objects of these content types will be searched on the content chooser." -msgstr "" +msgstr "搜尋內容時可供選擇的型別。" #: collective/cover/tiles/configuration_widgets/basewidget.pt:39 #: collective/cover/tiles/configuration_widgets/list.pt:47 @@ -368,11 +368,11 @@ msgstr "" #: collective/cover/controlpanel.py:34 msgid "Searchable Content Types" -msgstr "" +msgstr "型別選項" #: collective/cover/controlpanel.py:67 msgid "Settings for the collective.cover package" -msgstr "" +msgstr "collective.cover 模組設定" #: collective/cover/tiles/list.py:82 msgid "Show more... link" @@ -392,7 +392,7 @@ msgstr "" #: collective/cover/controlpanel.py:46 msgid "Styles" -msgstr "" +msgstr "風格選項" #: collective/cover/tiles/configuration_widgets/richtext.pt:50 msgid "Text length" @@ -420,7 +420,7 @@ msgstr "" #: collective/cover/controlpanel.py:26 msgid "This tiles will be available for layout creation." -msgstr "" +msgstr "建置佈局時可供選擇的圖磚類型。" #: collective/cover/tiles/list.py:78 msgid "Tile Title" From d1091d70d5c62eec4b1a337f9b69591e6c63f03c Mon Sep 17 00:00:00 2001 From: Rodrigo Ferreira de Souza Date: Wed, 12 Nov 2014 14:02:28 -0200 Subject: [PATCH 24/63] Basic changes --- src/collective/cover/tiles/carousel.py | 43 +++++++++++++++- .../cover/tiles/templates/carousel.pt | 6 ++- .../cover/widgets/textlines_sortable_input.pt | 13 +++-- .../cover/widgets/textlinessortable.py | 51 ++++++++++++++++--- 4 files changed, 98 insertions(+), 15 deletions(-) diff --git a/src/collective/cover/tiles/carousel.py b/src/collective/cover/tiles/carousel.py index f9b60a7e8..9ef44dff4 100644 --- a/src/collective/cover/tiles/carousel.py +++ b/src/collective/cover/tiles/carousel.py @@ -77,6 +77,47 @@ def autoplay(self): return self.data['autoplay'] + def get_title(self, item): + """ Gets the Title for the item. It will return the "Custom Title", if + it was set, if not, the Title for the item will be returned + + :param item: [required] The item for which we want the Title to + :type item: Content object + :returns: Title for the item + """ + # First we get the title for the item itself + title = item.Title() + uuid = self.get_uid(item) + data_mgr = ITileDataManager(self) + data = data_mgr.get() + uuids = data['uuids'] + if uuid in uuids: + if uuids[uuid].get('custom_title', u''): + # If we had a custom title set, then get that + title = uuids[uuid].get('custom_title') + return title + + def get_description(self, item): + """ Gets the Description for the item. It will return the + "Custom Desciption", if it was set, if not, the URL for the item will + be returned + + :param item: [required] The item for which we want the Description to + :type item: Content object + :returns: Description for the item + """ + # First we get the url for the item itself + description = item.Description() + uuid = self.get_uid(item) + data_mgr = ITileDataManager(self) + data = data_mgr.get() + uuids = data['uuids'] + if uuid in uuids: + if uuids[uuid].get('custom_description', u''): + # If we had a custom description set, then get that + description = uuids[uuid].get('custom_description') + return description + def get_url(self, item): """ Gets the URL for the item. It will return the "Custom URL", if it was set, if not, the URL for the item will be returned @@ -97,7 +138,7 @@ def get_url(self, item): data = data_mgr.get() uuids = data['uuids'] if uuid in uuids: - if uuids[uuid].get('custom_url', u""): + if uuids[uuid].get('custom_url', u''): # If we had a custom url set, then get that url = uuids[uuid].get('custom_url') return url diff --git a/src/collective/cover/tiles/templates/carousel.pt b/src/collective/cover/tiles/templates/carousel.pt index 552db4087..8ff822488 100644 --- a/src/collective/cover/tiles/templates/carousel.pt +++ b/src/collective/cover/tiles/templates/carousel.pt @@ -22,13 +22,15 @@ diff --git a/src/collective/cover/widgets/textlines_sortable_input.pt b/src/collective/cover/widgets/textlines_sortable_input.pt index 90686d89c..d1e161527 100644 --- a/src/collective/cover/widgets/textlines_sortable_input.pt +++ b/src/collective/cover/widgets/textlines_sortable_input.pt @@ -59,12 +59,17 @@ tal:attributes="src thumbnail/url; width thumbnail/width; height thumbnail/height" /> -

-

- + Custom Title + + Custom Description + Custom URL diff --git a/src/collective/cover/widgets/textlinessortable.py b/src/collective/cover/widgets/textlinessortable.py index 0396e9ee1..64a8529f4 100644 --- a/src/collective/cover/widgets/textlinessortable.py +++ b/src/collective/cover/widgets/textlinessortable.py @@ -55,6 +55,34 @@ def thumbnail(self, item): except: return None + def get_custom_title(self, uuid): + """ Returns the custom Title assigned to a specific item + + :param uuid: [required] The object's UUID + :type uuid: string + :returns: The custom Title + """ + title = u'' + uuids = self.context['uuids'] + if uuid in uuids: + values = uuids.get(uuid) + title = values.get('custom_title', u'') + return title + + def get_custom_description(self, uuid): + """ Returns the custom Description assigned to a specific item + + :param uuid: [required] The object's UUID + :type uuid: string + :returns: The custom Description + """ + description = u'' + uuids = self.context['uuids'] + if uuid in uuids: + values = uuids.get(uuid) + description = values.get('custom_description', u'') + return description + def get_custom_url(self, uuid): """ Returns the custom URL assigned to a specific item @@ -78,14 +106,21 @@ def extract(self): uuids = [i for i in values if i] results = dict() for index, uuid in enumerate(uuids): - if uuid: - custom_url = self.request.get( - '%s.custom_url.%s' % (self.name, uuid), "" - ) - results[uuid] = { - u'order': unicode(index), - u'custom_url': unicode(custom_url) - } + custom_title = self.request.get( + '{0}.custom_title.{1}'.format(self.name, uuid), '' + ) + custom_description = self.request.get( + '{0}.custom_description.{1}'.format(self.name, uuid), '' + ) + custom_url = self.request.get( + '{0}.custom_url.{1}'.format(self.name, uuid), '' + ) + results[uuid] = { + u'order': unicode(index), + u'custom_title': unicode(custom_title), + u'custom_description': unicode(custom_description), + u'custom_url': unicode(custom_url) + } return results From 3c4473bd5afa40b58f8fc0f7542cd3c1ff5b1016 Mon Sep 17 00:00:00 2001 From: Rodrigo Ferreira de Souza Date: Wed, 12 Nov 2014 22:11:26 -0200 Subject: [PATCH 25/63] Some fixes --- src/collective/cover/static/cover.js | 3 +- .../cover/widgets/textlines_sortable_input.pt | 70 +++++++++++++------ .../cover/widgets/textlinessortable.py | 44 +++++++++--- 3 files changed, 81 insertions(+), 36 deletions(-) diff --git a/src/collective/cover/static/cover.js b/src/collective/cover/static/cover.js index 8d9f1ff84..98f8720eb 100644 --- a/src/collective/cover/static/cover.js +++ b/src/collective/cover/static/cover.js @@ -148,7 +148,7 @@ $(document).ready(function() { }); }; - var textarea = carousel.find('textarea'); + var textarea = carousel.find('>textarea'); var sortable = carousel.find('.sortable'); textarea.hide(); @@ -171,4 +171,3 @@ $(document).ready(function() { } }); }); - diff --git a/src/collective/cover/widgets/textlines_sortable_input.pt b/src/collective/cover/widgets/textlines_sortable_input.pt index d1e161527..8f06c4d24 100644 --- a/src/collective/cover/widgets/textlines_sortable_input.pt +++ b/src/collective/cover/widgets/textlines_sortable_input.pt @@ -3,15 +3,32 @@ tal:omit-tag="">

@@ -53,27 +70,34 @@ tal:define="obj nocall:item/obj; uuid item/uuid" tal:attributes="data-uid uuid"> - - Custom Title - - Custom Description - - Custom URL - + +
+
+ + Custom Title +
+
+ + Custom Description +
+
+ + Custom URL +
+
- -
diff --git a/src/collective/cover/widgets/textlinessortable.py b/src/collective/cover/widgets/textlinessortable.py index 64a8529f4..ee30f4311 100644 --- a/src/collective/cover/widgets/textlinessortable.py +++ b/src/collective/cover/widgets/textlinessortable.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from collective.cover.widgets.interfaces import ITextLinesSortableWidget +from plone import api from plone.app.uuid.utils import uuidToObject from z3c.form import interfaces from z3c.form import widget @@ -62,12 +63,17 @@ def get_custom_title(self, uuid): :type uuid: string :returns: The custom Title """ + # Try to get custom title title = u'' uuids = self.context['uuids'] - if uuid in uuids: - values = uuids.get(uuid) - title = values.get('custom_title', u'') - return title + values = [uuids[i] for i in uuids if i == uuid] + if values: + title = values[0].get('custom_title', u'') + if title: + return title + # If didn't find, get object title + obj = uuidToObject(uuid) + return obj.Title() def get_custom_description(self, uuid): """ Returns the custom Description assigned to a specific item @@ -76,12 +82,17 @@ def get_custom_description(self, uuid): :type uuid: string :returns: The custom Description """ + # Try to get custom description description = u'' uuids = self.context['uuids'] - if uuid in uuids: - values = uuids.get(uuid) - description = values.get('custom_description', u'') - return description + values = [uuids[i] for i in uuids if i == uuid] + if values: + description = values[0].get('custom_description', u'') + if description: + return description + # If didn't find, get object description + obj = uuidToObject(uuid) + return obj.Description() def get_custom_url(self, uuid): """ Returns the custom URL assigned to a specific item @@ -90,11 +101,22 @@ def get_custom_url(self, uuid): :type uuid: string :returns: The custom URL """ + # Try to get custom url url = u'' uuids = self.context['uuids'] - if uuid in uuids: - values = uuids.get(uuid) - url = values.get('custom_url', u'') + values = [uuids[i] for i in uuids if i == uuid] + if values: + url = values[0].get('custom_url', u'') + if url: + return url + # If didn't find, get object url + obj = uuidToObject(uuid) + portal_properties = api.portal.get_tool(name='portal_properties') + use_view_action = portal_properties.site_properties.getProperty( + 'typesUseViewActionInListings', ()) + url = obj.absolute_url() + if obj.portal_type in use_view_action: + url = url + '/view' return url def extract(self): From 290eedcb2a3e63a1335bfa8fe83cf7f5a5c1d1e1 Mon Sep 17 00:00:00 2001 From: Rodrigo Ferreira de Souza Date: Thu, 13 Nov 2014 00:05:02 -0200 Subject: [PATCH 26/63] Added more tests --- .../cover/tests/test_carousel_tile.py | 56 +++++++++++++ .../tests/test_textlinessortable_widget.py | 79 +++++++++++++++++-- 2 files changed, 130 insertions(+), 5 deletions(-) diff --git a/src/collective/cover/tests/test_carousel_tile.py b/src/collective/cover/tests/test_carousel_tile.py index a51150f31..d9a8febad 100644 --- a/src/collective/cover/tests/test_carousel_tile.py +++ b/src/collective/cover/tests/test_carousel_tile.py @@ -104,6 +104,62 @@ def test_internal_structure(self): self.assertTrue(uuids[obj1.UID()]['order'] == u'0') self.assertTrue(uuids[obj2.UID()]['order'] == u'1') + def test_custom_title(self): + # we start with an empty tile + self.assertTrue(self.tile.is_empty()) + uuids = ITileDataManager(self.tile).get().get('uuids', None) + + self.assertIsNone(uuids) + + # now we 2 elements + obj1 = self.portal['my-document'] + obj2 = self.portal['my-image'] + + self.tile.populate_with_uids([ + obj1.UID(), obj2.UID() + ]) + + # For obj2 we will assign a custom_title + + uuids = ITileDataManager(self.tile).get().get('uuids', None) + uuids[obj2.UID()]['custom_title'] = u'New Title' + + title1 = self.tile.get_title(obj1) + title2 = self.tile.get_title(obj2) + + # Document object should be the same as Title + self.assertEqual(title1, obj1.Title()) + # First image should return the custom Title + self.assertEqual(title2, u'New Title') + + def test_custom_description(self): + # we start with an empty tile + self.assertTrue(self.tile.is_empty()) + uuids = ITileDataManager(self.tile).get().get('uuids', None) + + self.assertIsNone(uuids) + + # now we 2 elements + obj1 = self.portal['my-document'] + obj2 = self.portal['my-image'] + + self.tile.populate_with_uids([ + obj1.UID(), obj2.UID() + ]) + + # For obj2 we will assign a custom_description + + uuids = ITileDataManager(self.tile).get().get('uuids', None) + uuids[obj2.UID()]['custom_description'] = u'New Description' + + description1 = self.tile.get_description(obj1) + description2 = self.tile.get_description(obj2) + + # Document object should be the same as Description + self.assertEqual(description1, obj1.Description()) + # First image should return the custom URL + self.assertEqual(description2, u'New Description') + def test_custom_url(self): # we start with an empty tile self.assertTrue(self.tile.is_empty()) diff --git a/src/collective/cover/tests/test_textlinessortable_widget.py b/src/collective/cover/tests/test_textlinessortable_widget.py index c5644d061..256648fcb 100644 --- a/src/collective/cover/tests/test_textlinessortable_widget.py +++ b/src/collective/cover/tests/test_textlinessortable_widget.py @@ -48,6 +48,60 @@ def test_thumbnail(self): self.assertIsNotNone(widget.thumbnail(obj1)) self.assertIsNone(widget.thumbnail(obj2)) + def test_get_custom_title(self): + widget = TextLinesSortableWidget(self.request) + + obj1 = self.portal['my-image'] + obj2 = self.portal['my-image1'] + obj3 = self.portal['my-image2'] + + widget.context = {'uuids': { + obj1.UID(): {u'order': u'0', u'custom_title': u'custom_title'}, + obj2.UID(): {u'order': u'1', u'custom_title': u''}, + obj3.UID(): {u'order': u'2'}, + } + } + + self.assertEqual( + widget.get_custom_title(obj1.UID()), + u'custom_title' + ) + self.assertEqual( + widget.get_custom_title(obj2.UID()), + u'Test image #1' + ) + self.assertEqual( + widget.get_custom_title(obj3.UID()), + u'Test image #2' + ) + + def test_get_custom_description(self): + widget = TextLinesSortableWidget(self.request) + + obj1 = self.portal['my-image'] + obj2 = self.portal['my-image1'] + obj3 = self.portal['my-image2'] + + widget.context = {'uuids': { + obj1.UID(): {u'order': u'0', u'custom_description': u'custom_description'}, + obj2.UID(): {u'order': u'1', u'custom_description': u''}, + obj3.UID(): {u'order': u'2'}, + } + } + + self.assertEqual( + widget.get_custom_description(obj1.UID()), + u'custom_description' + ) + self.assertEqual( + widget.get_custom_description(obj2.UID()), + u'This image #1 was created for testing purposes' + ) + self.assertEqual( + widget.get_custom_description(obj3.UID()), + u'This image #2 was created for testing purposes' + ) + def test_get_custom_url(self): widget = TextLinesSortableWidget(self.request) @@ -63,8 +117,8 @@ def test_get_custom_url(self): } self.assertEqual(widget.get_custom_url(obj1.UID()), u'custom_url') - self.assertEqual(widget.get_custom_url(obj2.UID()), u'') - self.assertEqual(widget.get_custom_url(obj3.UID()), u'') + self.assertEqual(widget.get_custom_url(obj2.UID()), u'http://nohost/plone/my-image1/view') + self.assertEqual(widget.get_custom_url(obj3.UID()), u'http://nohost/plone/my-image2/view') def test_extract(self): name = 'uuid.field' @@ -76,9 +130,24 @@ def test_extract(self): widget.name = name expected = { - u'uuid1': {u'order': u'0', u'custom_url': u'custom_url'}, - u'uuid3': {u'order': u'1', u'custom_url': u''}, - u'uuid2': {u'order': u'2', u'custom_url': u''}, + u'uuid1': { + u'custom_description': u'', + u'custom_title': u'', + u'custom_url': u'custom_url', + u'order': u'0' + }, + u'uuid2': { + u'custom_description': u'', + u'custom_title': u'', + u'custom_url': u'', + u'order': u'2' + }, + u'uuid3': { + u'custom_description': u'', + u'custom_title': u'', + u'custom_url': u'', + u'order': u'1' + } } extracted_value = widget.extract() From 42ae77908c659bf7708ba916486d81703fe9be14 Mon Sep 17 00:00:00 2001 From: Rodrigo Ferreira de Souza Date: Thu, 13 Nov 2014 00:22:51 -0200 Subject: [PATCH 27/63] Fix tile carousel custom url robot test --- src/collective/cover/tests/test_carousel_tile.robot | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/collective/cover/tests/test_carousel_tile.robot b/src/collective/cover/tests/test_carousel_tile.robot index 31cf68add..a47ec0b73 100644 --- a/src/collective/cover/tests/test_carousel_tile.robot +++ b/src/collective/cover/tests/test_carousel_tile.robot @@ -120,10 +120,10 @@ Test Carousel Tile ${image_url} = Get Element Attribute xpath=.//div[@class='galleria-info-title']/a@href Should Be Equal ${image_url} ${PLONE_URL}/my-image1/view - + # Set custom URL Compose Cover Click Link css=${edit_link_selector} - Input Text xpath=.//div[@class='textline-sortable-element'][2]/input http://www.google.com + Input Text xpath=.//div[@class='textline-sortable-element'][2]//input[@class='custom-url-input'] http://www.google.com Click Button Save Sleep 2s Wait for carousel to load From e2b2d1a18232fef7a1b1282779325410d264b3f3 Mon Sep 17 00:00:00 2001 From: Rodrigo Ferreira de Souza Date: Thu, 13 Nov 2014 00:28:16 -0200 Subject: [PATCH 28/63] Fix tile list robot test --- src/collective/cover/tests/test_list_tile.robot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/collective/cover/tests/test_list_tile.robot b/src/collective/cover/tests/test_list_tile.robot index 6f456b9b1..6e1886b99 100644 --- a/src/collective/cover/tests/test_list_tile.robot +++ b/src/collective/cover/tests/test_list_tile.robot @@ -56,7 +56,7 @@ Test List Tile # drag&drop an Image Drag And Drop css=${image_selector} css=${tile_selector} Wait Until Page Contains Test image - Page Should Contain This image was created for testing purposes + Page Should Contain This image #2 was created for testing purposes # drag&drop a Link Drag And Drop css=${link_selector} css=${tile_selector} From 437186bd01abaf9ca9b6f927278a80dd47ae9fe0 Mon Sep 17 00:00:00 2001 From: Rodrigo Ferreira de Souza Date: Thu, 13 Nov 2014 00:59:12 -0200 Subject: [PATCH 29/63] =?UTF-8?q?Tentativa=20de=20corre=C3=A7=C3=A3o=20dos?= =?UTF-8?q?=20testes=20RF=20(bug=20ap=C3=B3s=20inserir=20mais=20imagens)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/collective/cover/tests/test_banner_tile.robot | 2 +- src/collective/cover/tests/test_basic_tile.robot | 2 +- src/collective/cover/tests/test_list_tile.robot | 4 ++-- src/collective/cover/tests/test_reverse_proxy.robot | 8 -------- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/collective/cover/tests/test_banner_tile.robot b/src/collective/cover/tests/test_banner_tile.robot index 20888bac9..604aeb8dc 100644 --- a/src/collective/cover/tests/test_banner_tile.robot +++ b/src/collective/cover/tests/test_banner_tile.robot @@ -9,7 +9,7 @@ Suite Teardown Close all browsers *** Variables *** ${banner_tile_location} 'collective.cover.banner' -${image_selector} .ui-draggable .contenttype-image +${image_selector} .ui-draggable .contenttype-image:first-child ${link_selector} .ui-draggable .contenttype-link ${news_item_selector} .ui-draggable .contenttype-news-item ${file_selector} .ui-draggable .contenttype-file diff --git a/src/collective/cover/tests/test_basic_tile.robot b/src/collective/cover/tests/test_basic_tile.robot index 41a590d10..b97e456a7 100644 --- a/src/collective/cover/tests/test_basic_tile.robot +++ b/src/collective/cover/tests/test_basic_tile.robot @@ -11,7 +11,7 @@ Suite Teardown Close all browsers ${basic_tile_location} 'collective.cover.basic' ${document_selector} .ui-draggable .contenttype-document ${file_selector} .ui-draggable .contenttype-file -${image_selector} .ui-draggable .contenttype-image +${image_selector} .ui-draggable .contenttype-image:first-child ${link_selector} .ui-draggable .contenttype-link ${tile_selector} div.tile-container div.tile ${news_item_selector} .ui-draggable .contenttype-news-item diff --git a/src/collective/cover/tests/test_list_tile.robot b/src/collective/cover/tests/test_list_tile.robot index 6e1886b99..6462da419 100644 --- a/src/collective/cover/tests/test_list_tile.robot +++ b/src/collective/cover/tests/test_list_tile.robot @@ -11,7 +11,7 @@ Suite Teardown Close all browsers ${list_tile_location} 'collective.cover.list' ${document_selector} .ui-draggable .contenttype-document ${file_selector} .ui-draggable .contenttype-file -${image_selector} .ui-draggable .contenttype-image +${image_selector} .ui-draggable .contenttype-image:first-child ${link_selector} .ui-draggable .contenttype-link ${news-item_selector} .ui-draggable .contenttype-news-item ${tile_selector} div.tile-container div.tile @@ -56,7 +56,7 @@ Test List Tile # drag&drop an Image Drag And Drop css=${image_selector} css=${tile_selector} Wait Until Page Contains Test image - Page Should Contain This image #2 was created for testing purposes + Page Should Contain This image was created for testing purposes # drag&drop a Link Drag And Drop css=${link_selector} css=${tile_selector} diff --git a/src/collective/cover/tests/test_reverse_proxy.robot b/src/collective/cover/tests/test_reverse_proxy.robot index 977619add..4e4b71d22 100644 --- a/src/collective/cover/tests/test_reverse_proxy.robot +++ b/src/collective/cover/tests/test_reverse_proxy.robot @@ -11,14 +11,6 @@ Suite Teardown Close all browsers *** Variables *** ${basic_tile_location} 'collective.cover.basic' -${document_selector} .ui-draggable .contenttype-document -${file_selector} .ui-draggable .contenttype-file -${image_selector} .ui-draggable .contenttype-image -${link_selector} .ui-draggable .contenttype-link -${tile_selector} div.tile-container div.tile -${news_item_selector} .ui-draggable .contenttype-news-item -${news_item_title} Test news item -${news_item_description} This news item was created for testing purposes *** Test cases *** From e8cfe30a0bd72bd4cd595c5cadd16e75686debd8 Mon Sep 17 00:00:00 2001 From: Rodrigo Ferreira de Souza Date: Thu, 13 Nov 2014 01:25:28 -0200 Subject: [PATCH 30/63] Fixed tests RF --- src/collective/cover/tests/test_banner_tile.robot | 2 +- src/collective/cover/tests/test_basic_tile.robot | 4 ++-- src/collective/cover/tests/test_list_tile.robot | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/collective/cover/tests/test_banner_tile.robot b/src/collective/cover/tests/test_banner_tile.robot index 604aeb8dc..20888bac9 100644 --- a/src/collective/cover/tests/test_banner_tile.robot +++ b/src/collective/cover/tests/test_banner_tile.robot @@ -9,7 +9,7 @@ Suite Teardown Close all browsers *** Variables *** ${banner_tile_location} 'collective.cover.banner' -${image_selector} .ui-draggable .contenttype-image:first-child +${image_selector} .ui-draggable .contenttype-image ${link_selector} .ui-draggable .contenttype-link ${news_item_selector} .ui-draggable .contenttype-news-item ${file_selector} .ui-draggable .contenttype-file diff --git a/src/collective/cover/tests/test_basic_tile.robot b/src/collective/cover/tests/test_basic_tile.robot index b97e456a7..ee9988f97 100644 --- a/src/collective/cover/tests/test_basic_tile.robot +++ b/src/collective/cover/tests/test_basic_tile.robot @@ -11,7 +11,7 @@ Suite Teardown Close all browsers ${basic_tile_location} 'collective.cover.basic' ${document_selector} .ui-draggable .contenttype-document ${file_selector} .ui-draggable .contenttype-file -${image_selector} .ui-draggable .contenttype-image:first-child +${image_selector} .ui-draggable .contenttype-image ${link_selector} .ui-draggable .contenttype-link ${tile_selector} div.tile-container div.tile ${news_item_selector} .ui-draggable .contenttype-news-item @@ -62,7 +62,7 @@ Test Basic Tile Compose Cover Open Content Chooser Drag And Drop css=${image_selector} css=${tile_selector} - Page Should Contain Test image + Wait Until Page Contains Element css=div.cover-basic-tile a img # move to the default view and check tile persisted Click Link link=View diff --git a/src/collective/cover/tests/test_list_tile.robot b/src/collective/cover/tests/test_list_tile.robot index 6462da419..a4b9be479 100644 --- a/src/collective/cover/tests/test_list_tile.robot +++ b/src/collective/cover/tests/test_list_tile.robot @@ -11,7 +11,7 @@ Suite Teardown Close all browsers ${list_tile_location} 'collective.cover.list' ${document_selector} .ui-draggable .contenttype-document ${file_selector} .ui-draggable .contenttype-file -${image_selector} .ui-draggable .contenttype-image:first-child +${image_selector} .ui-draggable .contenttype-image ${link_selector} .ui-draggable .contenttype-link ${news-item_selector} .ui-draggable .contenttype-news-item ${tile_selector} div.tile-container div.tile @@ -56,7 +56,7 @@ Test List Tile # drag&drop an Image Drag And Drop css=${image_selector} css=${tile_selector} Wait Until Page Contains Test image - Page Should Contain This image was created for testing purposes + Wait Until Page Contains Element css=div.cover-list-tile a img # drag&drop a Link Drag And Drop css=${link_selector} css=${tile_selector} From 9ca3ccccceb16e6467d7ee3df1c74a262e53790a Mon Sep 17 00:00:00 2001 From: Rodrigo Ferreira de Souza Date: Thu, 13 Nov 2014 14:22:22 -0200 Subject: [PATCH 31/63] Added robot tests for custom title and custom description --- .../cover/tests/test_carousel_tile.robot | 60 ++++++++++++++++++- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/src/collective/cover/tests/test_carousel_tile.robot b/src/collective/cover/tests/test_carousel_tile.robot index a47ec0b73..5e7882e34 100644 --- a/src/collective/cover/tests/test_carousel_tile.robot +++ b/src/collective/cover/tests/test_carousel_tile.robot @@ -14,6 +14,7 @@ ${image_selector} //div[@id="content-trees"]//li[@class="ui-draggable"]/a[@data ${image_title} //div[@class="galleria-info-title"]/a[text()='Test image']/.. ${image_selector2} //div[@id="content-trees"]//li[@class="ui-draggable"]/a[@data-ct-type="Image"]/span[text()='Test image #1']/.. ${image_title2} //div[@class="galleria-info-title"]/a[text()='Test image #1']/.. +${image_title_test} //div[@class="galleria-info-title"]/a[text()='New Title']/.. ${tile_selector} div.tile-container div.tile ${autoplay_id} collective-cover-carousel-autoplay-0 ${edit_link_selector} a.edit-tile-link @@ -109,14 +110,67 @@ Test Carousel Tile Compose Cover Page Should Contain options.autoplay = false; - # Test Custom URL functionality + ### Test Custom Title functionality + Click Link link=View + ${title} = Get Text xpath=.//div[@class='galleria-info-title']/a + Should Be Equal ${title} Test image + + # Go to the right + Click Element xpath=.//div[@class='galleria-image-nav-right'] + Wait Until Page Contains Element xpath=${image_title2} + ${title} = Get Text xpath=.//div[@class='galleria-info-title']/a + Should Be Equal ${title} Test image #1 + + # Set custom Title + Compose Cover + Click Link css=${edit_link_selector} + Input Text xpath=.//div[@class='textline-sortable-element'][2]//input[@class='custom-title-input'] New Title + Click Button Save + Sleep 2s Wait for carousel to load + + Click Link link=View + ${title} = Get Text xpath=.//div[@class='galleria-info-title']/a + Should Be Equal ${title} Test image + + # Go to the right + Click Element xpath=.//div[@class='galleria-image-nav-right'] + Wait Until Page Contains Element xpath=${image_title_test} + ${title} = Get Text xpath=.//div[@class='galleria-info-title']/a + Should Be Equal ${title} New Title + + ### Test Custom Description functionality + Click Link link=View + Textarea Value Should Be xpath=.//div[@class='galleria-info-description'] This image was created for testing purposes + + # Go to the right + Click Element xpath=.//div[@class='galleria-image-nav-right'] + Wait Until Page Contains Element xpath=${image_title_test} + Textarea Value Should Be xpath=.//div[@class='galleria-info-description'] This image #1 was created for testing purposes + + # Set custom Description + Compose Cover + Click Link css=${edit_link_selector} + Input Text xpath=.//div[@class='textline-sortable-element'][2]//textarea[@class='custom-description-input'] New Description + Click Button Save + Sleep 2s Wait for carousel to load + + Click Link link=View + Click Link link=View + Textarea Value Should Be xpath=.//div[@class='galleria-info-description'] This image was created for testing purposes + + # Go to the right + Click Element xpath=.//div[@class='galleria-image-nav-right'] + Wait Until Page Contains Element xpath=${image_title_test} + Textarea Value Should Be xpath=.//div[@class='galleria-info-description'] New Description + + ### Test Custom URL functionality Click Link link=View ${image_url} = Get Element Attribute xpath=.//div[@class='galleria-info-title']/a@href Should Be Equal ${image_url} ${PLONE_URL}/my-image/view # Go to the right Click Element xpath=.//div[@class='galleria-image-nav-right'] - Wait Until Page Contains Element xpath=${image_title2} + Wait Until Page Contains Element xpath=${image_title_test} ${image_url} = Get Element Attribute xpath=.//div[@class='galleria-info-title']/a@href Should Be Equal ${image_url} ${PLONE_URL}/my-image1/view @@ -133,7 +187,7 @@ Test Carousel Tile # Go to the right Click Element xpath=.//div[@class='galleria-image-nav-right'] - Wait Until Page Contains Element xpath=${image_title2} + Wait Until Page Contains Element xpath=${image_title_test} ${image_url} = Get Element Attribute xpath=.//div[@class='galleria-info-title']/a@href Should Be Equal ${image_url} http://www.google.com/ From fe98d05cc8da64c4232f493264af1c551cc9cd5e Mon Sep 17 00:00:00 2001 From: hvelarde Date: Thu, 13 Nov 2014 16:36:39 -0200 Subject: [PATCH 32/63] Update changelog --- CHANGES.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index c36323b7e..ab3911539 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,13 +6,14 @@ There's a frood who really knows where his towel is. 1.0a11 (unreleased) ^^^^^^^^^^^^^^^^^^^ -- Nothing changed yet. +- Allow to choose a custom title and description on items in a carousel tile (closes `#459`_). + [rodfersou] 1.0a10 (2014-10-31) ^^^^^^^^^^^^^^^^^^^ -- Allow to set a custom URL on elements for a Carousel (solves `#377`_). +- Allow to set a custom URL on elements for a Carousel (fixes `#377`_). [frapell] - If an Image content is included, redirect to view and not the image itself @@ -581,4 +582,5 @@ There's a frood who really knows where his towel is. .. _`#426`: https://github.com/collective/collective.cover/issues/426 .. _`#438`: https://github.com/collective/collective.cover/issues/438 .. _`#449`: https://github.com/collective/collective.cover/issues/449 +.. _`#459`: https://github.com/collective/collective.cover/issues/459 .. _`PloneFormGen`: https://pypi.python.org/pypi/Products.PloneFormGen From d7ff0b0ad50addc2aed8e07c9ab7d2fbd4b0e7ea Mon Sep 17 00:00:00 2001 From: hvelarde Date: Thu, 13 Nov 2014 16:37:40 -0200 Subject: [PATCH 33/63] Update docstrings --- src/collective/cover/tiles/carousel.py | 66 +++++++++++++++----------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/src/collective/cover/tiles/carousel.py b/src/collective/cover/tiles/carousel.py index 9ef44dff4..6bf4ea455 100644 --- a/src/collective/cover/tiles/carousel.py +++ b/src/collective/cover/tiles/carousel.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- - from collective.cover import _ from collective.cover.interfaces import ITileEditForm from collective.cover.tiles.list import IListTile @@ -32,8 +31,8 @@ class ICarouselTile(IListTile): - """A carousel based on the Galleria JavaScript image gallery framework. - """ + + """A carousel based on the Galleria JS image gallery framework.""" autoplay = schema.Bool( title=_(u'Auto play'), @@ -47,6 +46,8 @@ class ICarouselTile(IListTile): class CarouselTile(ListTile): + """A carousel based on the Galleria JS image gallery framework.""" + implements(ICarouselTile) index = ViewPageTemplateFile('templates/carousel.pt') @@ -55,10 +56,9 @@ class CarouselTile(ListTile): short_name = _(u'msg_short_name_carousel', default=u'Carousel') def populate_with_object(self, obj): - """ Add a list of elements to the list of items. This method will - append new elements to the already existing list of items. - If the object doesn't have an image associated, it will not be - included + """Add an object to the carousel. This method will append new + elements to the already existing list of items. If the object + does not have an image associated, it will not be included. :param uuids: The list of objects' UUIDs to be used :type uuids: List of strings @@ -74,16 +74,15 @@ def populate_with_object(self, obj): def autoplay(self): if self.data['autoplay'] is None: return True # default value - return self.data['autoplay'] def get_title(self, item): - """ Gets the Title for the item. It will return the "Custom Title", if - it was set, if not, the Title for the item will be returned + """Get the title of the item, or the custom title if set. - :param item: [required] The item for which we want the Title to + :param item: [required] The item for which we want the title :type item: Content object - :returns: Title for the item + :returns: the item title + :rtype: unicode """ # First we get the title for the item itself title = item.Title() @@ -98,13 +97,13 @@ def get_title(self, item): return title def get_description(self, item): - """ Gets the Description for the item. It will return the - "Custom Desciption", if it was set, if not, the URL for the item will - be returned + """Get the description of the item, or the custom description + if set. - :param item: [required] The item for which we want the Description to + :param item: [required] The item for which we want the description :type item: Content object - :returns: Description for the item + :returns: the item description + :rtype: unicode """ # First we get the url for the item itself description = item.Description() @@ -118,20 +117,27 @@ def get_description(self, item): description = uuids[uuid].get('custom_description') return description + def _get_types_that_use_view_action(self): + """Return a list of types that use the view action in listings. + + :returns: a list of content types + :rtype: tuple + """ + portal_properties = api.portal.get_tool('portal_properties') + return portal_properties.site_properties.getProperty( + 'typesUseViewActionInListings', ()) + def get_url(self, item): - """ Gets the URL for the item. It will return the "Custom URL", if - it was set, if not, the URL for the item will be returned + """Get the URL of the item, or the custom URL if set. - :param item: [required] The item for which we want the URL to + :param item: [required] The item for which we want the URL :type item: Content object - :returns: URL for the item + :returns: the item URL + :rtype: str """ - portal_properties = api.portal.get_tool(name='portal_properties') - use_view_action = portal_properties.site_properties.getProperty( - 'typesUseViewActionInListings', ()) # First we get the url for the item itself url = item.absolute_url() - if item.portal_type in use_view_action: + if item.portal_type in self._get_types_that_use_view_action(): url = url + '/view' uuid = self.get_uid(item) data_mgr = ITileDataManager(self) @@ -154,12 +160,14 @@ def init_js(self): class UUIDSFieldDataConverter(BaseDataConverter): + """A data converter using the field's ``fromUnicode()`` method.""" + adapts(IDict, ITextLinesSortableWidget) def toWidgetValue(self, value): - """ Converts the internal stored value into something that a z3c.form - widget understands + """Convert the internal stored value into something that a + z3c.form widget understands. :param value: [required] The internally stored value :type value: Dict @@ -170,8 +178,8 @@ def toWidgetValue(self, value): return '\r\n'.join([i[0] for i in ordered_uuids]) def toFieldValue(self, value): - """ Passes the value extracted from the widget to the internal - structure. In this case, the value expected is already formatted + """Pass the value extracted from the widget to the internal + structure. In this case, the value expected is already formatted. :param value: [required] The data extracted from the widget :type value: Dict From f5b31bd10cab6a01df06668a75da536a4bcfaf19 Mon Sep 17 00:00:00 2001 From: hvelarde Date: Thu, 13 Nov 2014 17:16:42 -0200 Subject: [PATCH 34/63] Do not pass silently as we have a bug over here @frapell this has to do with the implementation done in #451. To replicate: * add a carousel tile to a cover * drop an item to it * edit the tile to add a custom URL to the item * edit the tile again and remove the custom URL * drop another item into the tile the first item is replaced by the second. it would be great if you have time to check this and point us where the problem is; @rodfersou will work to fix it. --- src/collective/cover/tests/test_carousel_tile.robot | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/collective/cover/tests/test_carousel_tile.robot b/src/collective/cover/tests/test_carousel_tile.robot index 5e7882e34..de6527d96 100644 --- a/src/collective/cover/tests/test_carousel_tile.robot +++ b/src/collective/cover/tests/test_carousel_tile.robot @@ -30,8 +30,6 @@ Get Total Carousel Images *** Test cases *** Test Carousel Tile - [Tags] Expected Failure - Enable Autologin as Site Administrator Go to Homepage Create Cover Title Description From a44edc88d65802b50d497b57e6d95aaa827ae458 Mon Sep 17 00:00:00 2001 From: Franco Pellegrini Date: Sun, 16 Nov 2014 12:16:03 -0300 Subject: [PATCH 35/63] We don't really need a PersistentDict here. fixes gh-461 --- src/collective/cover/tests/test_carousel_tile.py | 5 ++--- src/collective/cover/tiles/list.py | 13 ++++++------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/collective/cover/tests/test_carousel_tile.py b/src/collective/cover/tests/test_carousel_tile.py index a51150f31..fd9c85fdf 100644 --- a/src/collective/cover/tests/test_carousel_tile.py +++ b/src/collective/cover/tests/test_carousel_tile.py @@ -5,7 +5,6 @@ from collective.cover.tiles.carousel import ICarouselTile from collective.cover.tiles.carousel import UUIDSFieldDataConverter from collective.cover.widgets.textlinessortable import TextLinesSortableWidget -from persistent.mapping import PersistentMapping from plone.tiles.interfaces import ITileDataManager from plone.uuid.interfaces import IUUID @@ -88,7 +87,7 @@ def test_internal_structure(self): uuids = ITileDataManager(self.tile).get().get('uuids', None) - self.assertTrue(isinstance(uuids, PersistentMapping)) + self.assertTrue(isinstance(uuids, dict)) self.assertTrue(len(uuids) == 1) self.assertTrue(uuids[obj1.UID()]['order'] == u'0') @@ -99,7 +98,7 @@ def test_internal_structure(self): uuids = ITileDataManager(self.tile).get().get('uuids', None) - self.assertTrue(isinstance(uuids, PersistentMapping)) + self.assertTrue(isinstance(uuids, dict)) self.assertTrue(len(uuids) == 2) self.assertTrue(uuids[obj1.UID()]['order'] == u'0') self.assertTrue(uuids[obj2.UID()]['order'] == u'1') diff --git a/src/collective/cover/tiles/list.py b/src/collective/cover/tiles/list.py index eaac63344..a9da43d5a 100644 --- a/src/collective/cover/tiles/list.py +++ b/src/collective/cover/tiles/list.py @@ -7,7 +7,6 @@ from collective.cover.tiles.base import IPersistentCoverTile from collective.cover.tiles.base import PersistentCoverTile from collective.cover.tiles.configuration_view import IDefaultConfigureForm -from persistent.mapping import PersistentMapping from plone import api from plone.app.uuid.utils import uuidToObject from plone.directives import form @@ -192,12 +191,12 @@ def populate_with_uids(self, uuids): old_data = data_mgr.get() if old_data['uuids'] is None: # If there is no content yet, just assign an empty dict - old_data['uuids'] = PersistentMapping() + old_data['uuids'] = dict() uuids_dict = old_data.get('uuids') - if not isinstance(uuids_dict, PersistentMapping): - # Make sure this is a PersistentMapping - uuids_dict = old_data['uuids'] = PersistentMapping() + if not isinstance(uuids_dict, dict): + # Make sure this is a dict + uuids_dict = old_data['uuids'] = dict() if uuids_dict and len(uuids_dict) > self.limit: # Do not allow adding more objects than the defined limit @@ -215,7 +214,7 @@ def populate_with_uids(self, uuids): for uuid in uuids: if uuid not in uuids_dict.keys(): - entry = PersistentMapping() + entry = dict() entry[u'order'] = unicode(order) uuids_dict[uuid] = entry order += 1 @@ -237,7 +236,7 @@ def replace_with_objects(self, uuids): data_mgr = ITileDataManager(self) old_data = data_mgr.get() # Clean old data - old_data['uuids'] = PersistentMapping() + old_data['uuids'] = dict() data_mgr.set(old_data) # Repopulate with clean list self.populate_with_uids(uuids) From 10bef5f86796bf7db52e520093dd3ece8b8a675f Mon Sep 17 00:00:00 2001 From: Rodrigo Ferreira de Souza Date: Mon, 17 Nov 2014 10:13:52 -0200 Subject: [PATCH 36/63] Some fixes in the test --- src/collective/cover/tests/test_carousel_tile.robot | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/collective/cover/tests/test_carousel_tile.robot b/src/collective/cover/tests/test_carousel_tile.robot index de6527d96..295e01002 100644 --- a/src/collective/cover/tests/test_carousel_tile.robot +++ b/src/collective/cover/tests/test_carousel_tile.robot @@ -138,12 +138,14 @@ Test Carousel Tile ### Test Custom Description functionality Click Link link=View - Textarea Value Should Be xpath=.//div[@class='galleria-info-description'] This image was created for testing purposes + ${description} = Get Text xpath=.//div[@class='galleria-info-description'] + Should Be Equal ${description} This image was created for testing purposes # Go to the right Click Element xpath=.//div[@class='galleria-image-nav-right'] Wait Until Page Contains Element xpath=${image_title_test} - Textarea Value Should Be xpath=.//div[@class='galleria-info-description'] This image #1 was created for testing purposes + ${description} = Get Text xpath=.//div[@class='galleria-info-description'] + Should Be Equal ${description} This image #1 was created for testing purposes # Set custom Description Compose Cover @@ -153,13 +155,14 @@ Test Carousel Tile Sleep 2s Wait for carousel to load Click Link link=View - Click Link link=View - Textarea Value Should Be xpath=.//div[@class='galleria-info-description'] This image was created for testing purposes + ${description} = Get Text xpath=.//div[@class='galleria-info-description'] + Should Be Equal ${description} This image was created for testing purposes # Go to the right Click Element xpath=.//div[@class='galleria-image-nav-right'] Wait Until Page Contains Element xpath=${image_title_test} - Textarea Value Should Be xpath=.//div[@class='galleria-info-description'] New Description + ${description} = Get Text xpath=.//div[@class='galleria-info-description'] + Should Be Equal ${description} New Description ### Test Custom URL functionality Click Link link=View From eb7cae6f610b50a183eb295d2c48668225f6d5cb Mon Sep 17 00:00:00 2001 From: Rodrigo Ferreira de Souza Date: Mon, 17 Nov 2014 13:29:45 -0200 Subject: [PATCH 37/63] Fix tests --- src/collective/cover/tests/test_carousel_tile.robot | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/collective/cover/tests/test_carousel_tile.robot b/src/collective/cover/tests/test_carousel_tile.robot index 295e01002..0965c5ba5 100644 --- a/src/collective/cover/tests/test_carousel_tile.robot +++ b/src/collective/cover/tests/test_carousel_tile.robot @@ -110,6 +110,7 @@ Test Carousel Tile ### Test Custom Title functionality Click Link link=View + Wait Until Page Contains Element xpath=${image_title} ${title} = Get Text xpath=.//div[@class='galleria-info-title']/a Should Be Equal ${title} Test image @@ -127,6 +128,7 @@ Test Carousel Tile Sleep 2s Wait for carousel to load Click Link link=View + Wait Until Page Contains Element xpath=${image_title} ${title} = Get Text xpath=.//div[@class='galleria-info-title']/a Should Be Equal ${title} Test image @@ -138,6 +140,7 @@ Test Carousel Tile ### Test Custom Description functionality Click Link link=View + Wait Until Page Contains Element xpath=${image_title} ${description} = Get Text xpath=.//div[@class='galleria-info-description'] Should Be Equal ${description} This image was created for testing purposes @@ -155,6 +158,7 @@ Test Carousel Tile Sleep 2s Wait for carousel to load Click Link link=View + Wait Until Page Contains Element xpath=${image_title} ${description} = Get Text xpath=.//div[@class='galleria-info-description'] Should Be Equal ${description} This image was created for testing purposes @@ -166,6 +170,7 @@ Test Carousel Tile ### Test Custom URL functionality Click Link link=View + Wait Until Page Contains Element xpath=${image_title} ${image_url} = Get Element Attribute xpath=.//div[@class='galleria-info-title']/a@href Should Be Equal ${image_url} ${PLONE_URL}/my-image/view @@ -183,6 +188,7 @@ Test Carousel Tile Sleep 2s Wait for carousel to load Click Link link=View + Wait Until Page Contains Element xpath=${image_title} ${image_url} = Get Element Attribute xpath=.//div[@class='galleria-info-title']/a@href Should Be Equal ${image_url} ${PLONE_URL}/my-image/view From e5d107d741719a4a51e570b4c3f529a3e9c7886e Mon Sep 17 00:00:00 2001 From: Kim Chee Leong Date: Fri, 21 Nov 2014 11:52:32 +0100 Subject: [PATCH 38/63] Added test to collection tile, to show date isn't handled properly. refs #463 --- .../cover/tests/test_collection_tile.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/collective/cover/tests/test_collection_tile.py b/src/collective/cover/tests/test_collection_tile.py index eb30fcf7d..003892e4b 100644 --- a/src/collective/cover/tests/test_collection_tile.py +++ b/src/collective/cover/tests/test_collection_tile.py @@ -200,3 +200,28 @@ def test_show_start_date_on_events(self): rendered = self.tile() tomorrow = api.portal.get_localized_time(tomorrow, long_format=True) self.assertIn(tomorrow, rendered) + + def test_date_on_items(self): + collection = self.portal['my-collection'] + image_query = [{ + 'i': 'Type', + 'o': 'plone.app.querystring.operation.string.is', + 'v': 'News Item', + }] + collection.setQuery(image_query) + collection.setSort_on('id') + self.tile.populate_with_object(collection) + + tile_config = self.tile.get_tile_configuration() + self.assertEqual(tile_config['date']['visibility'], u'on') + + content_listing_obj = collection.results()[0] + + date = self.tile.Date(content_listing_obj) + + self.assertFalse(hasattr(date, '__call__'), 'Date should not be calleable') + + fmt_date = self.portal.toLocalizedTime(content_listing_obj.Date(), True) + + rendered = self.tile() + self.assertTrue(fmt_date in rendered) From 1bef72fad463e450897fc008d4f35b86ec534565 Mon Sep 17 00:00:00 2001 From: Kim Chee Leong Date: Fri, 21 Nov 2014 12:05:45 +0100 Subject: [PATCH 39/63] Added check in Date function for content listing brains/objects. refs #463 --- src/collective/cover/tiles/base.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/collective/cover/tiles/base.py b/src/collective/cover/tiles/base.py index 569539e7f..fe07f23f4 100644 --- a/src/collective/cover/tiles/base.py +++ b/src/collective/cover/tiles/base.py @@ -5,6 +5,7 @@ from Acquisition import aq_base from Acquisition import aq_inner from Acquisition import aq_parent +from plone.app.contentlisting.catalog import CatalogContentListingObject from collective.cover import _ from collective.cover.config import PROJECTNAME from collective.cover.controlpanel import ICoverSettings @@ -351,7 +352,10 @@ def Date(self, brain): calendar = api.portal.get_tool('portal_calendar') # calendar_types lists all Event-like content types if brain.portal_type not in calendar.calendar_types: - return brain.Date + if type(brain) == CatalogContentListingObject: + return brain.Date() + else: + return brain.Date else: # an Event must have a start date assert brain.start is not Missing.Value From 6d0dc4350d92c3649ed6a8ec3de5f599e16e32d6 Mon Sep 17 00:00:00 2001 From: Kim Chee Leong Date: Fri, 21 Nov 2014 12:06:47 +0100 Subject: [PATCH 40/63] Updated test for dates on collection tile. refs #463 --- src/collective/cover/tests/test_collection_tile.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/collective/cover/tests/test_collection_tile.py b/src/collective/cover/tests/test_collection_tile.py index 003892e4b..4f5238c78 100644 --- a/src/collective/cover/tests/test_collection_tile.py +++ b/src/collective/cover/tests/test_collection_tile.py @@ -215,13 +215,16 @@ def test_date_on_items(self): tile_config = self.tile.get_tile_configuration() self.assertEqual(tile_config['date']['visibility'], u'on') + # Get the first news item from the collection content_listing_obj = collection.results()[0] date = self.tile.Date(content_listing_obj) - self.assertFalse(hasattr(date, '__call__'), 'Date should not be calleable') - fmt_date = self.portal.toLocalizedTime(content_listing_obj.Date(), True) + fmt_date = self.portal.toLocalizedTime(date, True) rendered = self.tile() - self.assertTrue(fmt_date in rendered) + self.assertTrue( + fmt_date in rendered, + 'Formatted date should be in rendered tile' + ) From adef6247ebb089ef40a2b9de1ed52e34401b8502 Mon Sep 17 00:00:00 2001 From: Kim Chee Leong Date: Fri, 21 Nov 2014 12:55:44 +0100 Subject: [PATCH 41/63] Updated change log --- CHANGES.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index ab3911539..e58c67350 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,7 +8,8 @@ There's a frood who really knows where his towel is. - Allow to choose a custom title and description on items in a carousel tile (closes `#459`_). [rodfersou] - +- Fix to show dates for results in collection tile (fixes `#463`_. + [kcleong] 1.0a10 (2014-10-31) ^^^^^^^^^^^^^^^^^^^ @@ -583,4 +584,5 @@ There's a frood who really knows where his towel is. .. _`#438`: https://github.com/collective/collective.cover/issues/438 .. _`#449`: https://github.com/collective/collective.cover/issues/449 .. _`#459`: https://github.com/collective/collective.cover/issues/459 +.. _`#463`: https://github.com/collective/collective.cover/issues/463 .. _`PloneFormGen`: https://pypi.python.org/pypi/Products.PloneFormGen From 7facf8833bc5d2407d6a442e61b6b97fbd4aaab6 Mon Sep 17 00:00:00 2001 From: Kim Chee Leong Date: Fri, 21 Nov 2014 12:56:14 +0100 Subject: [PATCH 42/63] small typo --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index e58c67350..95d1e0756 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,7 +8,7 @@ There's a frood who really knows where his towel is. - Allow to choose a custom title and description on items in a carousel tile (closes `#459`_). [rodfersou] -- Fix to show dates for results in collection tile (fixes `#463`_. +- Fix to show dates for results in collection tile (fixes `#463`_). [kcleong] 1.0a10 (2014-10-31) From 066d2a6467f2497410a430c42c86b78240bd4967 Mon Sep 17 00:00:00 2001 From: Kim Chee Leong Date: Wed, 26 Nov 2014 17:14:42 +0100 Subject: [PATCH 43/63] Use callable for checking is date is a function, refs #464 --- src/collective/cover/tests/test_collection_tile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/collective/cover/tests/test_collection_tile.py b/src/collective/cover/tests/test_collection_tile.py index 4f5238c78..e11afea4e 100644 --- a/src/collective/cover/tests/test_collection_tile.py +++ b/src/collective/cover/tests/test_collection_tile.py @@ -219,7 +219,7 @@ def test_date_on_items(self): content_listing_obj = collection.results()[0] date = self.tile.Date(content_listing_obj) - self.assertFalse(hasattr(date, '__call__'), 'Date should not be calleable') + self.assertFalse(callable(date), 'Date should not be calleable') fmt_date = self.portal.toLocalizedTime(date, True) From adb9c97e5fef5a1a0a7f6477fb78566f32392b2b Mon Sep 17 00:00:00 2001 From: Kim Chee Leong Date: Wed, 26 Nov 2014 17:17:01 +0100 Subject: [PATCH 44/63] Change log entries on top of others, #464 --- CHANGES.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 95d1e0756..e209aa1c8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,10 +6,10 @@ There's a frood who really knows where his towel is. 1.0a11 (unreleased) ^^^^^^^^^^^^^^^^^^^ -- Allow to choose a custom title and description on items in a carousel tile (closes `#459`_). - [rodfersou] - Fix to show dates for results in collection tile (fixes `#463`_). [kcleong] +- Allow to choose a custom title and description on items in a carousel tile (closes `#459`_). + [rodfersou] 1.0a10 (2014-10-31) ^^^^^^^^^^^^^^^^^^^ From 463c0c53cf654cab03d872468890cdcdf0049927 Mon Sep 17 00:00:00 2001 From: Kim Chee Leong Date: Wed, 26 Nov 2014 17:30:15 +0100 Subject: [PATCH 45/63] Check if date is callable and return callable or non-callable date, refs #464 --- src/collective/cover/tiles/base.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/collective/cover/tiles/base.py b/src/collective/cover/tiles/base.py index fe07f23f4..27540481e 100644 --- a/src/collective/cover/tiles/base.py +++ b/src/collective/cover/tiles/base.py @@ -5,7 +5,6 @@ from Acquisition import aq_base from Acquisition import aq_inner from Acquisition import aq_parent -from plone.app.contentlisting.catalog import CatalogContentListingObject from collective.cover import _ from collective.cover.config import PROJECTNAME from collective.cover.controlpanel import ICoverSettings @@ -352,10 +351,9 @@ def Date(self, brain): calendar = api.portal.get_tool('portal_calendar') # calendar_types lists all Event-like content types if brain.portal_type not in calendar.calendar_types: - if type(brain) == CatalogContentListingObject: - return brain.Date() - else: - return brain.Date + # return callable date for content listing objects or date + # as string for catalog/brain objects. + return brain.Date() if callable(brain.Date) else brain.Date else: # an Event must have a start date assert brain.start is not Missing.Value From 9cdc8cc049ba2eb16d7e5d44192895991b2af39b Mon Sep 17 00:00:00 2001 From: hvelarde Date: Tue, 9 Dec 2014 18:46:10 -0200 Subject: [PATCH 46/63] Extend upgrade step to update the structure of all tiles inheriting from the list tile (fixes #466) Fix upgrade step to use a dict instead of a PersistentMapping (refs. #467) --- CHANGES.rst | 6 ++++ src/collective/cover/tests/test_upgrades.py | 3 +- src/collective/cover/upgrades/__init__.py | 34 +++++++++++++++------ 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index e209aa1c8..3d148aab2 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,8 +6,13 @@ There's a frood who really knows where his towel is. 1.0a11 (unreleased) ^^^^^^^^^^^^^^^^^^^ +- Extend upgrade step to update the structure of all tiles inheriting from the list tile (fixes `#466`_). + Fix upgrade step to use a dict instead of a PersistentMapping. + [hvelarde] + - Fix to show dates for results in collection tile (fixes `#463`_). [kcleong] + - Allow to choose a custom title and description on items in a carousel tile (closes `#459`_). [rodfersou] @@ -585,4 +590,5 @@ There's a frood who really knows where his towel is. .. _`#449`: https://github.com/collective/collective.cover/issues/449 .. _`#459`: https://github.com/collective/collective.cover/issues/459 .. _`#463`: https://github.com/collective/collective.cover/issues/463 +.. _`#466`: https://github.com/collective/collective.cover/issues/466 .. _`PloneFormGen`: https://pypi.python.org/pypi/Products.PloneFormGen diff --git a/src/collective/cover/tests/test_upgrades.py b/src/collective/cover/tests/test_upgrades.py index 2553e103f..061cb9d3a 100644 --- a/src/collective/cover/tests/test_upgrades.py +++ b/src/collective/cover/tests/test_upgrades.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from collective.cover.config import DEFAULT_GRID_SYSTEM from collective.cover.testing import INTEGRATION_TESTING -from persistent.mapping import PersistentMapping from plone import api from plone.registry.interfaces import IRegistry from plone.tiles.interfaces import ITileDataManager @@ -259,7 +258,7 @@ def test_new_uuids_structure(self): self._do_upgrade_step(step) old_data = ITileDataManager(tile).get() self.assertFalse(isinstance(old_data['uuids'], list)) - self.assertTrue(isinstance(old_data['uuids'], PersistentMapping)) + self.assertTrue(isinstance(old_data['uuids'], dict)) self.assertEqual(old_data['uuids']['uuid1']['order'], u'0') self.assertEqual(old_data['uuids']['uuid2']['order'], u'2') self.assertEqual(old_data['uuids']['uuid3']['order'], u'1') diff --git a/src/collective/cover/upgrades/__init__.py b/src/collective/cover/upgrades/__init__.py index 5ccee64a5..59423dbe1 100644 --- a/src/collective/cover/upgrades/__init__.py +++ b/src/collective/cover/upgrades/__init__.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from collective.cover.config import PROJECTNAME from collective.cover.controlpanel import ICoverSettings -from persistent.mapping import PersistentMapping from plone import api from plone.registry.interfaces import IRegistry from plone.tiles.interfaces import ITileDataManager @@ -117,20 +116,37 @@ def change_configlet_permissions(context): def upgrade_carousel_tiles_custom_url(context): + """Update structure of tiles inheriting from the list tile.""" + + def get_tiles_to_update(): + """Return a list of all tiles inheriting from the list tile.""" + from collective.cover.tiles.list import IListTile + from plone.tiles.interfaces import ITileType + from zope.component import getUtility + from zope.schema.interfaces import IVocabularyFactory + name = 'collective.cover.EnabledTiles' + enabled_tiles = getUtility(IVocabularyFactory, name)(context) + tiles_to_update = [] + for i in enabled_tiles: + tile = getUtility(ITileType, i.value) + if issubclass(tile.schema, IListTile): + tiles_to_update.append(i.value) + return tiles_to_update + # Get covers covers = context.portal_catalog(portal_type='collective.cover.content') - logger.info('About to update %s covers' % len(covers)) + logger.info('About to update {0} objects'.format(len(covers))) + tiles_to_update = get_tiles_to_update() + logger.info('{0} tile types will be updated ({1})'.format( + len(tiles_to_update), ', '.join(tiles_to_update))) for cover in covers: obj = cover.getObject() - tile_ids = obj.list_tiles(types=[ - u'collective.cover.carousel', - u'collective.cover.list' - ]) + tile_ids = obj.list_tiles(types=tiles_to_update) for tile_id in tile_ids: tile = obj.get_tile(tile_id) old_data = ITileDataManager(tile).get() uuids = old_data['uuids'] - if isinstance(uuids, PersistentMapping): + if isinstance(uuids, dict): # This tile is fixed, carry on logger.info( 'Tile %s at %s was already updated' % @@ -145,11 +161,11 @@ def upgrade_carousel_tiles_custom_url(context): ) continue - new_data = PersistentMapping() + new_data = dict() order = 0 for uuid in uuids: if uuid not in new_data.keys(): - entry = PersistentMapping() + entry = dict() entry[u'order'] = unicode(order) new_data[uuid] = entry order += 1 From eabc5faf0401bd02930213b218c66141299959f6 Mon Sep 17 00:00:00 2001 From: Franco Pellegrini Date: Thu, 11 Dec 2014 17:10:36 -0300 Subject: [PATCH 47/63] Reimplemented in a better and reusable way --- src/collective/cover/upgrades/__init__.py | 32 +++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/collective/cover/upgrades/__init__.py b/src/collective/cover/upgrades/__init__.py index 59423dbe1..ae9e9fe2e 100644 --- a/src/collective/cover/upgrades/__init__.py +++ b/src/collective/cover/upgrades/__init__.py @@ -1,10 +1,13 @@ # -*- coding: utf-8 -*- from collective.cover.config import PROJECTNAME from collective.cover.controlpanel import ICoverSettings +from collective.cover.tiles.list import IListTile from plone import api from plone.registry.interfaces import IRegistry from plone.tiles.interfaces import ITileDataManager +from plone.tiles.interfaces import ITileType from zope.component import getUtility +from zope.schema.interfaces import IVocabularyFactory import logging @@ -115,28 +118,25 @@ def change_configlet_permissions(context): logger.info('configlet permissions updated') +def _get_tiles_inherit_from_list(context): + """Return a list of all tiles inheriting from the list tile.""" + name = 'collective.cover.EnabledTiles' + enabled_tiles = getUtility(IVocabularyFactory, name)(context) + tiles_to_update = [] + for i in enabled_tiles: + tile = getUtility(ITileType, i.value) + if issubclass(tile.schema, IListTile): + tiles_to_update.append(i.value) + return tiles_to_update + + def upgrade_carousel_tiles_custom_url(context): """Update structure of tiles inheriting from the list tile.""" - def get_tiles_to_update(): - """Return a list of all tiles inheriting from the list tile.""" - from collective.cover.tiles.list import IListTile - from plone.tiles.interfaces import ITileType - from zope.component import getUtility - from zope.schema.interfaces import IVocabularyFactory - name = 'collective.cover.EnabledTiles' - enabled_tiles = getUtility(IVocabularyFactory, name)(context) - tiles_to_update = [] - for i in enabled_tiles: - tile = getUtility(ITileType, i.value) - if issubclass(tile.schema, IListTile): - tiles_to_update.append(i.value) - return tiles_to_update - # Get covers covers = context.portal_catalog(portal_type='collective.cover.content') logger.info('About to update {0} objects'.format(len(covers))) - tiles_to_update = get_tiles_to_update() + tiles_to_update = _get_tiles_inherit_from_list(context) logger.info('{0} tile types will be updated ({1})'.format( len(tiles_to_update), ', '.join(tiles_to_update))) for cover in covers: From b42cda4bbae059abb61ce267b6d7d38b68a908b8 Mon Sep 17 00:00:00 2001 From: Franco Pellegrini Date: Thu, 11 Dec 2014 17:12:06 -0300 Subject: [PATCH 48/63] Add upgrade step for reverting tiles already migrated to PersistentMapping in 1.0a10. fixes gh-467 --- CHANGES.rst | 3 ++ src/collective/cover/profiles.zcml | 13 +++++ .../cover/profiles/default/metadata.xml | 2 +- src/collective/cover/tests/test_upgrades.py | 52 +++++++++++++++++++ src/collective/cover/upgrades/__init__.py | 45 ++++++++++++++++ 5 files changed, 114 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 3d148aab2..252248d1e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,9 @@ There's a frood who really knows where his towel is. 1.0a11 (unreleased) ^^^^^^^^^^^^^^^^^^^ +- Add upgrade step for reverting tiles already migrated to PersistentMapping in 1.0a10 + [frapell] + - Extend upgrade step to update the structure of all tiles inheriting from the list tile (fixes `#466`_). Fix upgrade step to use a dict instead of a PersistentMapping. [hvelarde] diff --git a/src/collective/cover/profiles.zcml b/src/collective/cover/profiles.zcml index 43ddc7104..fb7e50cc3 100644 --- a/src/collective/cover/profiles.zcml +++ b/src/collective/cover/profiles.zcml @@ -124,4 +124,17 @@ + + + + + + diff --git a/src/collective/cover/profiles/default/metadata.xml b/src/collective/cover/profiles/default/metadata.xml index 07eb4b5a3..3217224db 100644 --- a/src/collective/cover/profiles/default/metadata.xml +++ b/src/collective/cover/profiles/default/metadata.xml @@ -1,6 +1,6 @@ - 10 + 11 profile-collective.js.galleria:default profile-collective.js.jqueryui:default diff --git a/src/collective/cover/tests/test_upgrades.py b/src/collective/cover/tests/test_upgrades.py index 061cb9d3a..b679bf097 100644 --- a/src/collective/cover/tests/test_upgrades.py +++ b/src/collective/cover/tests/test_upgrades.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from collective.cover.config import DEFAULT_GRID_SYSTEM from collective.cover.testing import INTEGRATION_TESTING +from persistent.mapping import PersistentMapping from plone import api from plone.registry.interfaces import IRegistry from plone.tiles.interfaces import ITileDataManager @@ -262,3 +263,54 @@ def test_new_uuids_structure(self): self.assertEqual(old_data['uuids']['uuid1']['order'], u'0') self.assertEqual(old_data['uuids']['uuid2']['order'], u'2') self.assertEqual(old_data['uuids']['uuid3']['order'], u'1') + + +class Upgrade10to11TestCase(UpgradeTestCaseBase): + + def setUp(self): + UpgradeTestCaseBase.setUp(self, u'10', u'11') + + def test_upgrade_to_11_registrations(self): + version = self.setup.getLastVersionForProfile(self.profile_id)[0] + self.assertTrue(int(version) >= int(self.to_version)) + self.assertEqual(self._how_many_upgrades_to_do(), 1) + + def test_uuids_converted_to_dict(self): + title = u'Revert PersistentMapping back to dict' + step = self._get_upgrade_step(title) + self.assertIsNotNone(step) + + # simulate state on previous version + with api.env.adopt_roles(['Manager']): + api.content.create( + self.portal, 'collective.cover.content', + 'test-cover', + template_layout='Empty layout', + ) + + cover = self.portal['test-cover'] + + cover.cover_layout = ( + '[{"type": "row", "children": [{"data": {"layout-type": "column", ' + '"column-size": 16}, "type": "group", "children": [{"tile-type": ' + '"collective.cover.carousel", "type": "tile", "id": ' + '"ca6ba6675ef145e4a569c5e410af7511"}], "roles": ["Manager"]}]}]' + ) + + tile = cover.get_tile('ca6ba6675ef145e4a569c5e410af7511') + old_data = ITileDataManager(tile).get() + old_dict = PersistentMapping() + old_dict['uuid1'] = {'order': u'0'} + old_dict['uuid2'] = {'order': u'1'} + old_dict['uuid3'] = {'order': u'2'} + old_data['uuids'] = old_dict + ITileDataManager(tile).set(old_data) + + # run the upgrade step to validate the update + self._do_upgrade_step(step) + old_data = ITileDataManager(tile).get() + self.assertFalse(isinstance(old_data['uuids'], PersistentMapping)) + self.assertTrue(isinstance(old_data['uuids'], dict)) + self.assertEqual(old_data['uuids']['uuid1']['order'], u'0') + self.assertEqual(old_data['uuids']['uuid2']['order'], u'1') + self.assertEqual(old_data['uuids']['uuid3']['order'], u'2') diff --git a/src/collective/cover/upgrades/__init__.py b/src/collective/cover/upgrades/__init__.py index ae9e9fe2e..63142446e 100644 --- a/src/collective/cover/upgrades/__init__.py +++ b/src/collective/cover/upgrades/__init__.py @@ -177,3 +177,48 @@ def upgrade_carousel_tiles_custom_url(context): 'Tile %s at %s updated' % (tile_id, cover.getPath()) ) logger.info('Done') + + +def fix_persistentmap_to_dict(context): + """Internal structure was reverted from using PersistentMapping. + Fix tiles here""" + + # Get covers + covers = context.portal_catalog(portal_type='collective.cover.content') + logger.info('About to update {0} objects'.format(len(covers))) + tiles_to_update = _get_tiles_inherit_from_list(context) + logger.info('{0} tile types will be updated ({1})'.format( + len(tiles_to_update), ', '.join(tiles_to_update))) + for cover in covers: + obj = cover.getObject() + tile_ids = obj.list_tiles(types=tiles_to_update) + for tile_id in tile_ids: + tile = obj.get_tile(tile_id) + old_data = ITileDataManager(tile).get() + uuids = old_data['uuids'] + if isinstance(uuids, dict): + # This tile is fixed, carry on + logger.info( + 'Tile %s at %s was already updated' % + (tile_id, cover.getPath()) + ) + continue + if not uuids: + # This tile did not have data, so ignore + logger.info( + 'Tile %s at %s did not have any data' % + (tile_id, cover.getPath()) + ) + continue + + new_data = dict() + for k, v in uuids.items(): + new_data[k] = v + + old_data['uuids'] = new_data + ITileDataManager(tile).set(old_data) + + logger.info( + 'Tile %s at %s updated' % (tile_id, cover.getPath()) + ) + logger.info('Done') From 2f8b3fa9e134705da7133d479ff1a0059bf38aba Mon Sep 17 00:00:00 2001 From: hvelarde Date: Fri, 26 Dec 2014 11:53:26 -0200 Subject: [PATCH 49/63] Refactor CI for faster builds with container-based infrastructure and Docker (HT @datakurre) See: http://blog.travis-ci.com/2014-12-17-faster-builds-with-container-based-infrastructure/ --- .travis.yml | 8 ++++++-- travis.cfg | 19 +------------------ 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/.travis.yml b/.travis.yml index c66c922bc..405755c3e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,9 @@ language: python python: 2.7 +sudo: false +cache: + directories: + - eggs env: global: - ARTIFACTS_AWS_REGION=sa-east-1 @@ -16,8 +20,8 @@ matrix: - env: PLONE_VERSION=5.0 fast_finish: true install: - - sed -ie "s#travis-4.x.cfg#travis-$PLONE_VERSION.x.cfg#" travis.cfg - - sed -ie "s#versions-4.x.cfg#versions-$PLONE_VERSION.x.cfg#" travis.cfg + - sed -ie "s#test-4.3#test-$PLONE_VERSION#" buildout.cfg + - sed -ie "s#versions-4.3#versions-$PLONE_VERSION#" buildout.cfg - test $JQUERY && sed -ie "s#plone.app.jquery = 1.7.2#plone.app.jquery = $JQUERY#" versions-4.3.x.cfg || true - mkdir -p buildout-cache/downloads - python bootstrap.py -c travis.cfg -v 2.2.1 diff --git a/travis.cfg b/travis.cfg index bb002fd56..8bfb3ed17 100644 --- a/travis.cfg +++ b/travis.cfg @@ -1,26 +1,9 @@ [buildout] -extends = - https://raw.github.com/collective/buildout.plonetest/master/travis-4.x.cfg - https://raw.github.com/collective/buildout.plonetest/master/qa.cfg - https://raw.github.com/plone/plone.app.robotframework/master/versions.cfg - versions-4.x.cfg - +extends = buildout.cfg package-name = collective.cover package-extras = [test] test-eggs = Pillow parts += node -[code-analysis] -directory = ${buildout:directory}/src/collective/cover -pre-commit-hook = False -flake8-ignore = E501 -flake8-max-complexity = 12 -csslint = True -csslint-bin = bin/csslint -jshint = True -jshint-bin = bin/jshint -debug-statements = True -prefer-single-quotes = True - [versions] zc.buildout = 2.2.1 From 9e779a68817c11e9f13a57366a784be58a9d03e6 Mon Sep 17 00:00:00 2001 From: Fred van Dijk Date: Mon, 26 Jan 2015 23:04:12 +0100 Subject: [PATCH 50/63] Add warning in the developer docs on grid systems: switching the default grid system also means you should pay extra attention to the preset/saved cover layouts as these can contain invalid grid sizes. --- CHANGES.rst | 3 +++ docs/developer.rst | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 252248d1e..0056c4b7a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,9 @@ There's a frood who really knows where his towel is. 1.0a11 (unreleased) ^^^^^^^^^^^^^^^^^^^ +- Add warning in the developer docs that switching the default grid system also means you should pay extra attention to the preset/saved cover layouts as these can contain invalid grid sizes. + [fredvd] + - Add upgrade step for reverting tiles already migrated to PersistentMapping in 1.0a10 [frapell] diff --git a/docs/developer.rst b/docs/developer.rst index b5e1a0fc5..4fa6c1c7f 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -240,3 +240,15 @@ WARNING: Switching the grid system will apply to all new and existing covers. If you already made layouts for a 16 column grid and switch to e.g. a 12 column grid, you will have to manually update all existing covers (their layout is not recalculated automatically). + +WARNING 2: Cover support saving layout designs by exporting them to a +json/python dictionairy which is stored in the Plone registry. Cover also +inserts a few of these saved preset layouts upon installation, check +registry.xml in collective.cover source. + +You always start a new cover by selecting one of these layout designs on the +Cover add page. If you switch from the default 16 column deco grid to another +grid with another maximum cover size, these saved layouts however will probably +still contain a 16 width column and this can mock up your design in small ways. +If you do swith grid system, make sure you clear the default cover layouts +and/or insert your own with the correct max column size. \ No newline at end of file From 6692b522ba97292d09c0d308917084cd9b94bb94 Mon Sep 17 00:00:00 2001 From: Fred van Dijk Date: Tue, 27 Jan 2015 17:21:33 +0100 Subject: [PATCH 51/63] Allow new empty carousel tiles to be edited in compose mode. --- CHANGES.rst | 3 +++ src/collective/cover/tests/test_carousel_tile.robot | 5 +++++ src/collective/cover/tiles/carousel.py | 6 ++++++ 3 files changed, 14 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 0056c4b7a..d35a79ce0 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,9 @@ There's a frood who really knows where his towel is. 1.0a11 (unreleased) ^^^^^^^^^^^^^^^^^^^ +- Allow new empty carousel tiles to be edited in compose mode. (fixes `#472`_). + [fredvd] + - Add warning in the developer docs that switching the default grid system also means you should pay extra attention to the preset/saved cover layouts as these can contain invalid grid sizes. [fredvd] diff --git a/src/collective/cover/tests/test_carousel_tile.robot b/src/collective/cover/tests/test_carousel_tile.robot index 0965c5ba5..4a6348fad 100644 --- a/src/collective/cover/tests/test_carousel_tile.robot +++ b/src/collective/cover/tests/test_carousel_tile.robot @@ -43,6 +43,11 @@ Test Carousel Tile Compose Cover Page Should Contain This carousel is empty; open the content chooser and drag-and-drop some items here. + # Test if we can edit the cover without any content added to it yet + Click Link css=${edit_link_selector} + Page Should Contain Edit Carousel Tile + Click Button Cancel + # drag&drop an Image Open Content Chooser Click Element link=Content tree diff --git a/src/collective/cover/tiles/carousel.py b/src/collective/cover/tiles/carousel.py index 6bf4ea455..4f08108fb 100644 --- a/src/collective/cover/tiles/carousel.py +++ b/src/collective/cover/tiles/carousel.py @@ -173,6 +173,12 @@ def toWidgetValue(self, value): :type value: Dict :returns: A string with UUIDs separated by \r\n """ + + # A new carousel tile has no items, populate_with_uids has not been + # called yet, so incoming uuids is not an empty dict() but None + if value is None: + return '' + ordered_uuids = [(k, v) for k, v in value.items()] ordered_uuids.sort(key=lambda x: x[1]['order']) return '\r\n'.join([i[0] for i in ordered_uuids]) From cb35639cd4261ce4f436ee7c515bfe8946dae0a1 Mon Sep 17 00:00:00 2001 From: Fred van Dijk Date: Wed, 28 Jan 2015 11:02:12 +0100 Subject: [PATCH 52/63] Don't ignore dropped objects on carousel tiles if the image is added to the object using a Dexterity behavior. (fixes '#473`_`). --- CHANGES.rst | 3 +++ src/collective/cover/tiles/carousel.py | 9 +++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index d35a79ce0..ef61b02e8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,9 @@ There's a frood who really knows where his towel is. 1.0a11 (unreleased) ^^^^^^^^^^^^^^^^^^^ +- Don't ignore dropped objects on carousel tiles if the image is added to the object using a Dexterity behavior. (fixes '#473`_`). + [fredvd] + - Allow new empty carousel tiles to be edited in compose mode. (fixes `#472`_). [fredvd] diff --git a/src/collective/cover/tiles/carousel.py b/src/collective/cover/tiles/carousel.py index 4f08108fb..78806fc39 100644 --- a/src/collective/cover/tiles/carousel.py +++ b/src/collective/cover/tiles/carousel.py @@ -58,16 +58,13 @@ class CarouselTile(ListTile): def populate_with_object(self, obj): """Add an object to the carousel. This method will append new elements to the already existing list of items. If the object - does not have an image associated, it will not be included. + does not have an image associated, it will not be included and silently + ignored. :param uuids: The list of objects' UUIDs to be used :type uuids: List of strings """ - try: - image_size = obj.restrictedTraverse('@@images').getImageSize() - except: - image_size = None - if not image_size: + if not self._has_image_field(obj): return super(CarouselTile, self).populate_with_object(obj) From ea6b875dd541b1cd9a75320010bc44d61795d59a Mon Sep 17 00:00:00 2001 From: Rodrigo Ferreira de Souza Date: Mon, 9 Feb 2015 22:21:25 -0200 Subject: [PATCH 53/63] Checked if custom attributes were changed --- .../tests/test_textlinessortable_widget.py | 21 +++++++++++++----- .../cover/widgets/textlinessortable.py | 22 ++++++++++++++----- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/collective/cover/tests/test_textlinessortable_widget.py b/src/collective/cover/tests/test_textlinessortable_widget.py index 256648fcb..0bca5db1c 100644 --- a/src/collective/cover/tests/test_textlinessortable_widget.py +++ b/src/collective/cover/tests/test_textlinessortable_widget.py @@ -121,28 +121,37 @@ def test_get_custom_url(self): self.assertEqual(widget.get_custom_url(obj3.UID()), u'http://nohost/plone/my-image2/view') def test_extract(self): + obj1 = self.portal['my-image'] + obj2 = self.portal['my-image1'] + obj3 = self.portal['my-image2'] + uuids = [ + obj1.UID(), + obj3.UID(), + obj2.UID() + ] + name = 'uuid.field' - self.request.set(name, u'uuid1\r\nuuid3\r\nuuid2') - self.request.set(u'%s.custom_url.uuid1' % name, u'custom_url') - self.request.set(u'%s.custom_url.uuid2' % name, u'') + self.request.set(name, u'\r\n'.join(uuids)) + self.request.set(u'{0}.custom_url.{1}'.format(name, obj1.UID()), u'custom_url') + self.request.set(u'{0}.custom_url.{1}'.format(name, obj2.UID()), u'') widget = TextLinesSortableWidget(self.request) widget.name = name expected = { - u'uuid1': { + obj1.UID(): { u'custom_description': u'', u'custom_title': u'', u'custom_url': u'custom_url', u'order': u'0' }, - u'uuid2': { + obj2.UID(): { u'custom_description': u'', u'custom_title': u'', u'custom_url': u'', u'order': u'2' }, - u'uuid3': { + obj3.UID(): { u'custom_description': u'', u'custom_title': u'', u'custom_url': u'', diff --git a/src/collective/cover/widgets/textlinessortable.py b/src/collective/cover/widgets/textlinessortable.py index ee30f4311..3f173617d 100644 --- a/src/collective/cover/widgets/textlinessortable.py +++ b/src/collective/cover/widgets/textlinessortable.py @@ -124,25 +124,35 @@ def extract(self): :returns: A dictionary with the information """ + portal_properties = api.portal.get_tool(name='portal_properties') + use_view_action = portal_properties.site_properties.getProperty( + 'typesUseViewActionInListings', ()) values = self.request.get(self.name).split('\r\n') uuids = [i for i in values if i] results = dict() for index, uuid in enumerate(uuids): + obj = uuidToObject(uuid) + results[uuid] = { + u'order': unicode(index) + } custom_title = self.request.get( '{0}.custom_title.{1}'.format(self.name, uuid), '' ) + if custom_title != obj.Title(): + results[uuid][u'custom_title'] = custom_title custom_description = self.request.get( '{0}.custom_description.{1}'.format(self.name, uuid), '' ) + if custom_description != obj.Description(): + results[uuid][u'custom_description'] = custom_description custom_url = self.request.get( '{0}.custom_url.{1}'.format(self.name, uuid), '' ) - results[uuid] = { - u'order': unicode(index), - u'custom_title': unicode(custom_title), - u'custom_description': unicode(custom_description), - u'custom_url': unicode(custom_url) - } + url = obj.absolute_url() + if obj.portal_type in use_view_action: + url = url + '/view' + if custom_url != url: + results[uuid][u'custom_url'] = custom_url return results From 9fbc4dd33905205c590612992662576da95b369b Mon Sep 17 00:00:00 2001 From: Rodrigo Ferreira de Souza Date: Mon, 9 Feb 2015 22:23:02 -0200 Subject: [PATCH 54/63] Updated changelog --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index d35a79ce0..b6142720a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -25,6 +25,9 @@ There's a frood who really knows where his towel is. - Allow to choose a custom title and description on items in a carousel tile (closes `#459`_). [rodfersou] +- Fix to check if custom attributes were changed from default value (closes `#476`_). + [rodfersou] + 1.0a10 (2014-10-31) ^^^^^^^^^^^^^^^^^^^ From 07b9a75ccd55ed8d9a4ca91dab683fc52dc1a2eb Mon Sep 17 00:00:00 2001 From: Rodrigo Ferreira de Souza Date: Mon, 9 Feb 2015 22:26:07 -0200 Subject: [PATCH 55/63] Code review --- src/collective/cover/widgets/textlinessortable.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/collective/cover/widgets/textlinessortable.py b/src/collective/cover/widgets/textlinessortable.py index 3f173617d..942eee61e 100644 --- a/src/collective/cover/widgets/textlinessortable.py +++ b/src/collective/cover/widgets/textlinessortable.py @@ -139,12 +139,12 @@ def extract(self): '{0}.custom_title.{1}'.format(self.name, uuid), '' ) if custom_title != obj.Title(): - results[uuid][u'custom_title'] = custom_title + results[uuid][u'custom_title'] = unicode(custom_title) custom_description = self.request.get( '{0}.custom_description.{1}'.format(self.name, uuid), '' ) if custom_description != obj.Description(): - results[uuid][u'custom_description'] = custom_description + results[uuid][u'custom_description'] = unicode(custom_description) custom_url = self.request.get( '{0}.custom_url.{1}'.format(self.name, uuid), '' ) @@ -152,7 +152,7 @@ def extract(self): if obj.portal_type in use_view_action: url = url + '/view' if custom_url != url: - results[uuid][u'custom_url'] = custom_url + results[uuid][u'custom_url'] = unicode(custom_url) return results From 248c9da9a71b125a34d82d03f7734d6bdd5c5107 Mon Sep 17 00:00:00 2001 From: Rodrigo Ferreira de Souza Date: Tue, 10 Feb 2015 13:09:40 -0200 Subject: [PATCH 56/63] Update changelog --- CHANGES.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index b6142720a..d82f79aac 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,9 @@ There's a frood who really knows where his towel is. 1.0a11 (unreleased) ^^^^^^^^^^^^^^^^^^^ +- Fix to check if custom attributes were changed from default value (closes `#476`_). + [rodfersou] + - Allow new empty carousel tiles to be edited in compose mode. (fixes `#472`_). [fredvd] @@ -25,9 +28,6 @@ There's a frood who really knows where his towel is. - Allow to choose a custom title and description on items in a carousel tile (closes `#459`_). [rodfersou] -- Fix to check if custom attributes were changed from default value (closes `#476`_). - [rodfersou] - 1.0a10 (2014-10-31) ^^^^^^^^^^^^^^^^^^^ @@ -603,4 +603,5 @@ There's a frood who really knows where his towel is. .. _`#459`: https://github.com/collective/collective.cover/issues/459 .. _`#463`: https://github.com/collective/collective.cover/issues/463 .. _`#466`: https://github.com/collective/collective.cover/issues/466 +.. _`#476`: https://github.com/collective/collective.cover/issues/476 .. _`PloneFormGen`: https://pypi.python.org/pypi/Products.PloneFormGen From 76e6ddb08b6c23b6fff5f705b40fd4e94af4e089 Mon Sep 17 00:00:00 2001 From: hvelarde Date: Fri, 20 Feb 2015 14:10:09 -0200 Subject: [PATCH 57/63] Use latest version of setuptools under Plone 5.0 also --- versions-5.0.x.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/versions-5.0.x.cfg b/versions-5.0.x.cfg index 013e20519..edc9c9911 100644 --- a/versions-5.0.x.cfg +++ b/versions-5.0.x.cfg @@ -5,3 +5,6 @@ plone.app.drafts = 1.0a2 plone.app.tiles = 1.0.1 plone.formwidget.namedfile = 1.0.10 plone.tiles = 1.2 + +# use latest version of setuptools +setuptools = From 9f5729784bf2e56ff4297ae980146d90ed9a148c Mon Sep 17 00:00:00 2001 From: hvelarde Date: Fri, 20 Feb 2015 14:10:09 -0200 Subject: [PATCH 58/63] Use latest version of setuptools under Plone 5.0 also --- versions-5.0.x.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/versions-5.0.x.cfg b/versions-5.0.x.cfg index 013e20519..edc9c9911 100644 --- a/versions-5.0.x.cfg +++ b/versions-5.0.x.cfg @@ -5,3 +5,6 @@ plone.app.drafts = 1.0a2 plone.app.tiles = 1.0.1 plone.formwidget.namedfile = 1.0.10 plone.tiles = 1.2 + +# use latest version of setuptools +setuptools = From dc19566a7202f0f6a7ecfbe7a58f5d8cd00acfda Mon Sep 17 00:00:00 2001 From: hvelarde Date: Fri, 20 Feb 2015 14:07:02 -0200 Subject: [PATCH 59/63] Fix carousel RF test Move autoplay tests to Integration layer and hack another randomly failing feature. --- .../cover/tests/test_carousel_tile.py | 33 ++++++++++++++----- .../cover/tests/test_carousel_tile.robot | 25 +++----------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/collective/cover/tests/test_carousel_tile.py b/src/collective/cover/tests/test_carousel_tile.py index 3e800fd91..4d5d5c588 100644 --- a/src/collective/cover/tests/test_carousel_tile.py +++ b/src/collective/cover/tests/test_carousel_tile.py @@ -19,6 +19,11 @@ def setUp(self): self.tile.__name__ = u'collective.cover.carousel' self.tile.id = u'test' + def _update_tile_data(self): + # tile's data attribute is cached; reinstantiate it + self.tile = self.cover.restrictedTraverse( + '@@{0}/{1}'.format(str(self.tile.__name__), str(self.tile.id))) + @unittest.expectedFailure # FIXME: raises BrokenImplementation def test_interface(self): self.interface = ICarouselTile @@ -36,6 +41,22 @@ def test_accepted_content_types(self): def test_tile_is_empty(self): self.assertTrue(self.tile.is_empty()) + def test_autoplay(self): + # autoplay is True when tile is empty + self.assertTrue(self.tile.autoplay()) + # but Galleria init code is not rendered + self.assertNotIn('options.autoplay = true', self.tile()) + obj = self.portal['my-image'] + self.tile.populate_with_object(obj) + self.assertIn('options.autoplay = true', self.tile()) + data_mgr = ITileDataManager(self.tile) + data = data_mgr.get() + data['autoplay'] = False + data_mgr.set(data) + self._update_tile_data() + self.assertFalse(self.tile.autoplay()) + self.assertIn('options.autoplay = false', self.tile()) + def test_crud(self): # we start with an empty tile self.assertTrue(self.tile.is_empty()) @@ -47,9 +68,7 @@ def test_crud(self): self.tile.populate_with_object(obj1) self.tile.populate_with_object(obj2) - # tile's data attribute is cached; reinstantiate it - self.tile = self.cover.restrictedTraverse( - '@@{0}/{1}'.format('collective.cover.carousel', 'test')) + self._update_tile_data() # Document should not have been added self.assertEqual(len(self.tile.results()), 1) @@ -59,18 +78,14 @@ def test_crud(self): # next, we replace the list of objects with a different one obj3 = self.portal['my-image1'] self.tile.replace_with_objects([IUUID(obj3, None)]) - # tile's data attribute is cached; reinstantiate it - self.tile = self.cover.restrictedTraverse( - '@@{0}/{1}'.format('collective.cover.carousel', 'test')) + self._update_tile_data() self.assertNotIn(obj1, self.tile.results()) self.assertNotIn(obj2, self.tile.results()) self.assertIn(obj3, self.tile.results()) # finally, we remove it from the list; the tile must be empty again self.tile.remove_item(obj3.UID()) - # tile's data attribute is cached; reinstantiate it - self.tile = self.cover.restrictedTraverse( - '@@{0}/{1}'.format('collective.cover.carousel', 'test')) + self._update_tile_data() self.assertTrue(self.tile.is_empty()) def test_internal_structure(self): diff --git a/src/collective/cover/tests/test_carousel_tile.robot b/src/collective/cover/tests/test_carousel_tile.robot index 4a6348fad..e1c6bd47a 100644 --- a/src/collective/cover/tests/test_carousel_tile.robot +++ b/src/collective/cover/tests/test_carousel_tile.robot @@ -52,9 +52,6 @@ Test Carousel Tile Open Content Chooser Click Element link=Content tree Drag And Drop xpath=${image_selector} css=${tile_selector} - # The carousel was previously empty, so autoplay=false, so we might not see the carousel updated - # Wait Until Page Contains Test image - # Page Should Contain This image was created for testing purposes # move to the default view and check tile persisted Click Link link=View @@ -72,7 +69,9 @@ Test Carousel Tile Click Element link=Content tree Drag And Drop xpath=${image_selector2} css=${tile_selector} - # Need to change view before second image is loaded + # HACK: object not being added to tile when dropped; just, try again + # Galleria messing around the DOM? + Drag And Drop xpath=${image_selector2} css=${tile_selector} # move to the default view and check tile persisted Click Link link=View @@ -96,22 +95,8 @@ Test Carousel Tile ${images} = Get Total Carousel Images Should Be Equal '${images}' '2' - # carousel autoplay is enabled - Page Should Contain options.autoplay = true; - - # edit the tile - Compose Cover - Click Link css=${edit_link_selector} - Page Should Contain Element css=.textline-sortable-element - # disable carousel autoplay - Unselect Checkbox ${autoplay_id} - Click Button Save - Wait Until Page Contains Test image - Page Should Contain This image was created for testing purposes - - # carousel autoplay is now disabled. Sometimes we need to reload the page. - Compose Cover - Page Should Contain options.autoplay = false; + # testing against Galleria is a PITA; slow down the process from here + Set Selenium Speed .5 ### Test Custom Title functionality Click Link link=View From f38550e350b8290c32a3c1889ba4c1b3fd61127d Mon Sep 17 00:00:00 2001 From: hvelarde Date: Mon, 23 Feb 2015 16:20:20 -0300 Subject: [PATCH 60/63] Keep docstring below 72 chars --- src/collective/cover/tiles/carousel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/collective/cover/tiles/carousel.py b/src/collective/cover/tiles/carousel.py index 78806fc39..3ac3661c7 100644 --- a/src/collective/cover/tiles/carousel.py +++ b/src/collective/cover/tiles/carousel.py @@ -58,8 +58,8 @@ class CarouselTile(ListTile): def populate_with_object(self, obj): """Add an object to the carousel. This method will append new elements to the already existing list of items. If the object - does not have an image associated, it will not be included and silently - ignored. + does not have an image associated, it will not be included and + silently ignored. :param uuids: The list of objects' UUIDs to be used :type uuids: List of strings From de379ff42c92194ba774fe3a7b021707b574ef39 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Wed, 4 Feb 2015 14:15:52 +0100 Subject: [PATCH 61/63] enable compatibility with jQuery's "noConflict" mode in contentchooser.js The "coveractions" object avoids the issue by using only "jQuery" (instead of "$") and I was not sure if the symbol is intentionally global so I didn't include it in the new anonymous function. --- src/collective/cover/static/contentchooser.js | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/collective/cover/static/contentchooser.js b/src/collective/cover/static/contentchooser.js index 9b3c48b6a..6963ebb25 100644 --- a/src/collective/cover/static/contentchooser.js +++ b/src/collective/cover/static/contentchooser.js @@ -1,3 +1,4 @@ +(function ($) { var ajaxSearchRequest = []; function contentSearchFilter(url) { var queryVal = $("#contentchooser-content-search-input").val(); @@ -15,16 +16,14 @@ function contentSearchFilter(url) { return false; } -(function ($) { - $.fn.liveDraggable = function (opts) { - $(document).on("mouseover", this.selector, function() { - if (!$(this).data("init")) { - $(this).data("init", true).draggable(opts); - } - }); - return $(); - }; -}(jQuery)); +$.fn.liveDraggable = function (opts) { + $(document).on("mouseover", this.selector, function() { + if (!$(this).data("init")) { + $(this).data("init", true).draggable(opts); + } + }); + return $(); +}; function contentchooserMaker(options) { @@ -172,6 +171,7 @@ $(function() { }); }); +}(jQuery)); var coveractions = { @@ -380,6 +380,7 @@ var coveractions = { coveractions.preInit(); +(function ($) { function filterOnKeyUp() { $("#contentchooser-content-search-button").css("display", "none"); $(".contentchooser-content-trees").keyup(function() { @@ -395,3 +396,4 @@ function filterOnKeyUp() { $(document).ready(function() { filterOnKeyUp(); }); +}(jQuery)); From 33061c8cafa9ac409d8b5d98c744e9a2498976d0 Mon Sep 17 00:00:00 2001 From: Rodrigo Ferreira de Souza Date: Mon, 23 Feb 2015 17:40:43 -0300 Subject: [PATCH 62/63] Fix JS Intentation --- src/collective/cover/static/contentchooser.js | 378 +++++++++--------- 1 file changed, 189 insertions(+), 189 deletions(-) diff --git a/src/collective/cover/static/contentchooser.js b/src/collective/cover/static/contentchooser.js index 6963ebb25..1f23969da 100644 --- a/src/collective/cover/static/contentchooser.js +++ b/src/collective/cover/static/contentchooser.js @@ -1,176 +1,176 @@ (function ($) { -var ajaxSearchRequest = []; -function contentSearchFilter(url) { - var queryVal = $("#contentchooser-content-search-input").val(); - var page = 0; - var data = {'q': queryVal, 'page': page}; - ajaxSearchRequest.push($.ajax({ - url: url, - data: data, - success: function(info) { - $("#contentchooser-content-search #recent .item-list").html(info); - $("#contentchooser-content-search #recent .item-list li ul").css("display", "none"); - return false; - } - })); - return false; -} - -$.fn.liveDraggable = function (opts) { - $(document).on("mouseover", this.selector, function() { - if (!$(this).data("init")) { - $(this).data("init", true).draggable(opts); - } - }); - return $(); -}; - - -function contentchooserMaker(options) { - var windowId = options.windowId; - var droppable = options.droppable; - var draggable = options.draggable; - var draggable_acepted = options.draggable_acepted; - var dropped = options.dropped; - - //items inside contentchooser should be draggable - $(draggable).liveDraggable({ - scroll: false, - helper: "clone" - }); - - $(droppable).droppable({ - activeClass: 'ui-state-default', - accept: draggable_acepted, - hoverClass: 'content-drop-hover ui-state-hover', - drop: dropped - }); - - // TODO: check if the current contentchooser requires any tabs - $(windowId + " ul.formTabs").tabs("div.panes > div"); -} - -$(function() { - // Live search content tree - coveractions.liveSearch('#contentchooser-content-trees','.item-list li','#filter-count'); - - $(document).on("click", ".item-list a.next", function(e){ - $.ajax({ - url: "@@content-search", - data: {'page': $(e.currentTarget).attr('data-page'), - 'q': $(e.currentTarget).attr('data-query')}, - async: false - }).done(function(data) { - $('.item-list a.next').replaceWith(data); - }); - return false; - }); - - $(document).on("click", "#recent .contentchooser-clear", function(e){ - $(e.currentTarget).prev().children("input").val(""); + var ajaxSearchRequest = []; + function contentSearchFilter(url) { + var queryVal = $("#contentchooser-content-search-input").val(); + var page = 0; + var data = {'q': queryVal, 'page': page}; ajaxSearchRequest.push($.ajax({ - url: portal_url + "/@@content-search", + url: url, + data: data, success: function(info) { - $("#contentchooser-content-search #recent .item-list").html(info); - $("#contentchooser-content-search #recent .item-list li ul").css("display", "none"); - return false; + $("#contentchooser-content-search #recent .item-list").html(info); + $("#contentchooser-content-search #recent .item-list li ul").css("display", "none"); + return false; } })); return false; - }); + } + + $.fn.liveDraggable = function (opts) { + $(document).on("mouseover", this.selector, function() { + if (!$(this).data("init")) { + $(this).data("init", true).draggable(opts); + } + }); + return $(); + }; - $(document).on("click", "#content-trees .contentchooser-clear", function(e){ - $(e.currentTarget).prev().children("input").val(""); - coveractions.getFolderContents(portal_url, '@@jsonbytype'); - return false; - }); - if($("#contentchooser-content-search").length) { - var content_name = $("#contentchooser-content-search-compose-button").text(); - $("#content").prepend("
"+content_name+"
"); + function contentchooserMaker(options) { + var windowId = options.windowId; + var droppable = options.droppable; + var draggable = options.draggable; + var draggable_acepted = options.draggable_acepted; + var dropped = options.dropped; - $( "#contentchooser-content-search" ).resizable({ - maxHeight:411, - minHeight: 411, - minWidth: 350, - maxWidth: 540 + //items inside contentchooser should be draggable + $(draggable).liveDraggable({ + scroll: false, + helper: "clone" }); - } + $(droppable).droppable({ + activeClass: 'ui-state-default', + accept: draggable_acepted, + hoverClass: 'content-drop-hover ui-state-hover', + drop: dropped + }); - $("#contentchooser-content-search-button").click(function(e) { - e.preventDefault(); - e.stopPropagation(); - var dataUrl = $(this).attr("data-url"); - contentSearchFilter(dataUrl); - return false; - }); + // TODO: check if the current contentchooser requires any tabs + $(windowId + " ul.formTabs").tabs("div.panes > div"); + } - contentchooserMaker({ - draggable: '#contentchooser-content-search .item-list li', - draggable_acepted: function(e) { - var ct = $(this).data('tileValidCt'); - var valid = $.inArray($(e).find('a').data('ctType'), ct); - var isDroppable = $(this).attr("data-is-droppable"); + $(function() { + // Live search content tree + coveractions.liveSearch('#contentchooser-content-trees','.item-list li','#filter-count'); - if(isDroppable === "False" || $(e).attr('id') === 'contentchooser-content-search') { - return false; - } - if(!ct && $($(e).context).parent().attr("class") === "item-list") { - return true; - } - return valid !== -1? true : false; - }, - windowId: '#contentchooser-content-search', - droppable: '#content .tile', - dropped: function(event, ui) { - var tile = $(this); - var tile_type = tile.attr("data-tile-type"); - var tile_id = tile.attr("id"); - var ct_uid = ui.draggable.attr("uid"); - tile.find('.loading-mask').addClass('show'); + $(document).on("click", ".item-list a.next", function(e){ $.ajax({ - url: "@@updatetilecontent", - data: {'tile-type': tile_type, 'tile-id': tile_id, 'uid': ct_uid}, + url: "@@content-search", + data: {'page': $(e.currentTarget).attr('data-page'), + 'q': $(e.currentTarget).attr('data-query')}, + async: false + }).done(function(data) { + $('.item-list a.next').replaceWith(data); + }); + return false; + }); + + $(document).on("click", "#recent .contentchooser-clear", function(e){ + $(e.currentTarget).prev().children("input").val(""); + ajaxSearchRequest.push($.ajax({ + url: portal_url + "/@@content-search", success: function(info) { - tile.html(info); - tile.find('.loading-mask').removeClass('show'); - TitleMarkupSetup(); + $("#contentchooser-content-search #recent .item-list").html(info); + $("#contentchooser-content-search #recent .item-list li ul").css("display", "none"); return false; } + })); + return false; + }); + + $(document).on("click", "#content-trees .contentchooser-clear", function(e){ + $(e.currentTarget).prev().children("input").val(""); + coveractions.getFolderContents(portal_url, '@@jsonbytype'); + return false; + }); + + if($("#contentchooser-content-search").length) { + var content_name = $("#contentchooser-content-search-compose-button").text(); + $("#content").prepend("
"+content_name+"
"); + + $( "#contentchooser-content-search" ).resizable({ + maxHeight:411, + minHeight: 411, + minWidth: 350, + maxWidth: 540 }); + } - }); + $("#contentchooser-content-search-button").click(function(e) { + e.preventDefault(); + e.stopPropagation(); + var dataUrl = $(this).attr("data-url"); + contentSearchFilter(dataUrl); + return false; + }); + contentchooserMaker({ + draggable: '#contentchooser-content-search .item-list li', + draggable_acepted: function(e) { + var ct = $(this).data('tileValidCt'); + var valid = $.inArray($(e).find('a').data('ctType'), ct); + var isDroppable = $(this).attr("data-is-droppable"); + + if(isDroppable === "False" || $(e).attr('id') === 'contentchooser-content-search') { + return false; + } + if(!ct && $($(e).context).parent().attr("class") === "item-list") { + return true; + } + return valid !== -1? true : false; + }, + windowId: '#contentchooser-content-search', + droppable: '#content .tile', + dropped: function(event, ui) { + var tile = $(this); + var tile_type = tile.attr("data-tile-type"); + var tile_id = tile.attr("id"); + var ct_uid = ui.draggable.attr("uid"); + tile.find('.loading-mask').addClass('show'); + $.ajax({ + url: "@@updatetilecontent", + data: {'tile-type': tile_type, 'tile-id': tile_id, 'uid': ct_uid}, + success: function(info) { + tile.html(info); + tile.find('.loading-mask').removeClass('show'); + TitleMarkupSetup(); + return false; + } + }); + } + }); - $( "#contentchooser-content-search" ).draggable({ - start: function(event, ui) { - $(this).removeClass("right"); - }, - cancel: '.item-list, #contentchooser-content-search-input, #contentchooser-content-trees' - }); - $("#contentchooser-content-show-button").click(function() { - var offset = $(this).offset(); - $("#contentchooser-content-search").css("display", "block"); - $("#contentchooser-content-search").offset({'top':offset.top}); - }); - - $("#contentchooser-content-search .close").click(function(e) { - e.preventDefault(); - $("#contentchooser-content-search").css("display", "none"); - }); - $(document).on("click", "#contentchooser-content-search #content-tree .item-list li", function(e) { - e.stopPropagation(); - var child = $(this).children("ul"); - if (child.is(":visible")) { - child.css("display", "none"); - } else { - child.css("display", "block"); - } - }); -}); + + $( "#contentchooser-content-search" ).draggable({ + start: function(event, ui) { + $(this).removeClass("right"); + }, + cancel: '.item-list, #contentchooser-content-search-input, #contentchooser-content-trees' + }); + $("#contentchooser-content-show-button").click(function() { + var offset = $(this).offset(); + $("#contentchooser-content-search").css("display", "block"); + $("#contentchooser-content-search").offset({'top':offset.top}); + }); + + $("#contentchooser-content-search .close").click(function(e) { + e.preventDefault(); + $("#contentchooser-content-search").css("display", "none"); + }); + $(document).on("click", "#contentchooser-content-search #content-tree .item-list li", function(e) { + e.stopPropagation(); + var child = $(this).children("ul"); + if (child.is(":visible")) { + child.css("display", "none"); + } else { + child.css("display", "block"); + } + }); + + }); }(jQuery)); @@ -179,10 +179,10 @@ var coveractions = { * Context URL to be used for all AJAX call */ /* - var call_context = $("head base").attr('href'); - if (call_context.charAt(call_context.length - 1) !== '/') { - call_context = call_context + '/'; - } + var call_context = $("head base").attr('href'); + if (call_context.charAt(call_context.length - 1) !== '/') { + call_context = call_context + '/'; + } */ call_context : portal_url + '/', current_path : document.location.href, @@ -274,9 +274,9 @@ var coveractions = { content_type : "application/x-www-form-urlencoded", type : 'POST', data : "searchtext=" + - (jQuery('input:text[id=contentchooser-content-trees][name=contentchooser-content-trees]').val() || '') + - "&rooted='False'" + "&document_base_url=" + - encodeURIComponent(d.baseURI), + (jQuery('input:text[id=contentchooser-content-trees][name=contentchooser-content-trees]').val() || '') + + "&rooted='False'" + "&document_base_url=" + + encodeURIComponent(d.baseURI), success : function(text) { var html = ""; var data = jQuery.parseJSON(text); @@ -348,26 +348,26 @@ var coveractions = { // selectoroutput: Displays number of items that meet the search criteria // Eg. coveractions.liveSearch('#contentchooser-content-trees','.item-list li','#filter-count'); jQuery(selector).keyup(function(){ -     // Retrieve the input field text and reset the count to zero -     var filter = $(this).val(), count = 0; - -     // Loop through the items list -     jQuery(selectorlist).each(function(){ -         // If the list item does not contain the text phrase fade it out -         if (jQuery(this).text().search(new RegExp(filter, "i")) < 0) { -             jQuery(this).fadeOut(); -         // Show the list item if the phrase matches and increase the count by 1 -         } else { -             jQuery(this).show(); -             count++; -         } - -     }); - -     // Update the count +     // Retrieve the input field text and reset the count to zero +     var filter = $(this).val(), count = 0; + +     // Loop through the items list +     jQuery(selectorlist).each(function(){ +         // If the list item does not contain the text phrase fade it out +         if (jQuery(this).text().search(new RegExp(filter, "i")) < 0) { +             jQuery(this).fadeOut(); +         // Show the list item if the phrase matches and increase the count by 1 +         } else { +             jQuery(this).show(); +             count++; +         } + +     }); + +     // Update the count if (filter !== ''){ -     var numberItems = count; -     jQuery(selectoroutput).text(" "+ count + " Results"); +     var numberItems = count; +     jQuery(selectoroutput).text(" "+ count + " Results"); } else { jQuery(selectoroutput).text(""); } @@ -381,19 +381,19 @@ coveractions.preInit(); (function ($) { -function filterOnKeyUp() { - $("#contentchooser-content-search-button").css("display", "none"); - $(".contentchooser-content-trees").keyup(function() { - var i = 0; - for(i=0; i Date: Wed, 25 Feb 2015 18:13:31 -0300 Subject: [PATCH 63/63] Add waffle.io badge --- README.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.rst b/README.rst index f189a9cd9..dd8594625 100644 --- a/README.rst +++ b/README.rst @@ -75,6 +75,10 @@ Mostly Harmless :alt: Travis CI badge :target: http://travis-ci.org/collective/collective.cover +.. image:: https://badge.waffle.io/collective/collective.cover.png?label=ready&title=Ready + :alt: Stories in Ready + :target: https://waffle.io/collective/collective.cover + .. image:: https://coveralls.io/repos/collective/collective.cover/badge.png?branch=master :alt: Coveralls badge :target: https://coveralls.io/r/collective/collective.cover