imio.smartweb.core 1.3__py3-none-any.whl → 1.3.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. imio/smartweb/core/browser/configure.zcml +16 -0
  2. imio/smartweb/core/browser/search/search.py +15 -0
  3. imio/smartweb/core/browser/sitemap.py +162 -110
  4. imio/smartweb/core/contents/rest/base.py +5 -2
  5. imio/smartweb/core/contents/rest/campaign/endpoint.py +3 -3
  6. imio/smartweb/core/contents/rest/directory/endpoint.py +11 -3
  7. imio/smartweb/core/contents/rest/events/endpoint.py +11 -3
  8. imio/smartweb/core/contents/rest/news/endpoint.py +11 -3
  9. imio/smartweb/core/contents/rest/search/endpoint.py +4 -1
  10. imio/smartweb/core/contents/rest/utils.py +3 -3
  11. imio/smartweb/core/interfaces.py +2 -0
  12. imio/smartweb/core/profiles/default/metadata.xml +1 -1
  13. imio/smartweb/core/profiles/default/types/imio.smartweb.SectionTimestampedPublications.xml +4 -0
  14. imio/smartweb/core/profiles/default/types.xml +1 -0
  15. imio/smartweb/core/profiles/default/workflows.xml +1 -0
  16. imio/smartweb/core/tests/test_ideabox.py +33 -1
  17. imio/smartweb/core/tests/test_rest.py +1 -3
  18. imio/smartweb/core/tests/test_sitemap.py +98 -40
  19. imio/smartweb/core/tests/test_vocabulary.py +18 -0
  20. imio/smartweb/core/upgrades/configure.zcml +19 -0
  21. imio/smartweb/core/upgrades/profiles/1061_to_1062/types/imio.smartweb.SectionTimestampedPublications.xml +1 -5
  22. imio/smartweb/core/upgrades/profiles/1066_to_1067/types/imio.smartweb.SectionTimestampedPublications.xml +39 -0
  23. imio/smartweb/core/viewlets/offcanvas.pt +2 -0
  24. imio/smartweb/core/webcomponents/build/css/919.smartweb-webcomponents-compiled.css +1 -1
  25. imio/smartweb/core/webcomponents/build/js/373.smartweb-webcomponents-compiled.js +1 -1
  26. imio/smartweb/core/webcomponents/build/js/666.smartweb-webcomponents-compiled.js +1 -1
  27. imio/smartweb/core/webcomponents/build/js/919.smartweb-webcomponents-compiled.js +1 -1
  28. imio/smartweb/core/webcomponents/build/js/922.smartweb-webcomponents-compiled.js +1 -1
  29. imio/smartweb/core/webcomponents/src/components/Search/Search.jsx +54 -9
  30. imio/smartweb/core/webcomponents/src/components/Search/Search.scss +9 -0
  31. imio/smartweb/core/webcomponents/src/utils/translation.js +12 -0
  32. {imio.smartweb.core-1.3.dist-info → imio.smartweb.core-1.3.2.dist-info}/METADATA +27 -1
  33. {imio.smartweb.core-1.3.dist-info → imio.smartweb.core-1.3.2.dist-info}/RECORD +39 -38
  34. /imio.smartweb.core-1.3-py3.12-nspkg.pth → /imio.smartweb.core-1.3.2-py3.12-nspkg.pth +0 -0
  35. {imio.smartweb.core-1.3.dist-info → imio.smartweb.core-1.3.2.dist-info}/LICENSE.GPL +0 -0
  36. {imio.smartweb.core-1.3.dist-info → imio.smartweb.core-1.3.2.dist-info}/LICENSE.rst +0 -0
  37. {imio.smartweb.core-1.3.dist-info → imio.smartweb.core-1.3.2.dist-info}/WHEEL +0 -0
  38. {imio.smartweb.core-1.3.dist-info → imio.smartweb.core-1.3.2.dist-info}/namespace_packages.txt +0 -0
  39. {imio.smartweb.core-1.3.dist-info → imio.smartweb.core-1.3.2.dist-info}/top_level.txt +0 -0
@@ -96,6 +96,22 @@
96
96
  layer="imio.smartweb.core.interfaces.IImioSmartwebCoreLayer"
97
97
  />
98
98
 
99
+ <adapter
100
+ factory="imio.smartweb.core.browser.sitemap.SitemapNavtreeStrategy"
101
+ provides="imio.smartweb.core.browser.sitemap.ISmartwebNavtreeStrategy"
102
+ for="*
103
+ imio.smartweb.core.interfaces.IImioSmartwebCoreLayer"
104
+ />
105
+
106
+ <browser:page
107
+ name="sitemap_builder_view"
108
+ for="*"
109
+ class=".sitemap.CatalogSiteMap"
110
+ allowed_attributes="siteMap"
111
+ permission="zope.Public"
112
+ layer="imio.smartweb.core.interfaces.IImioSmartwebCoreLayer"
113
+ />
114
+
99
115
  <browser:page
100
116
  name="events_view"
101
117
  for="*"
@@ -1,5 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
+ from imio.smartweb.core.contents.rest.search.endpoint import get_default_view_url
3
4
  from plone import api
4
5
  from Products.CMFPlone.browser.search import Search
5
6
  from Products.CMFPlone.utils import normalizeString
@@ -68,6 +69,20 @@ class Search(Search):
68
69
 
69
70
  return json.dumps(response)
70
71
 
72
+ @property
73
+ def are_views_available(self):
74
+ news = False if get_default_view_url("news") == "" else True
75
+ events = False if get_default_view_url("events") == "" else True
76
+ directory = False if get_default_view_url("directory") == "" else True
77
+ return json.dumps({"news": news, "events": events, "directory": directory})
78
+
79
+ @property
80
+ def context_user_has_roles(self):
81
+ # is current user has more role(s) than just authenticated
82
+ if len(api.user.get_roles()) > 1:
83
+ return True
84
+ return False
85
+
71
86
  @property
72
87
  def current_language(self):
73
88
  return api.portal.get_current_language()[:2]
@@ -1,49 +1,111 @@
1
+ from Acquisition import aq_inner
1
2
  from BTrees.OOBTree import OOBTree
2
- from imio.smartweb.core.config import DIRECTORY_URL
3
- from imio.smartweb.core.config import EVENTS_URL
4
- from imio.smartweb.core.config import NEWS_URL
5
- from imio.smartweb.core.contents.rest.utils import get_auth_sources_response
6
- from imio.smartweb.core.contents.rest.utils import get_entity_id
7
- from plone import api
8
- from plone.base.interfaces import IPloneSiteRoot
3
+ from imio.smartweb.core.contents.rest.directory.endpoint import DirectoryEndpointGet
4
+ from imio.smartweb.core.contents.rest.events.endpoint import EventsEndpointGet
5
+ from imio.smartweb.core.contents.rest.news.endpoint import NewsEndpointGet
6
+ from imio.smartweb.core.interfaces import IImioSmartwebCoreLayer
7
+ from plone.app.layout.navigation.navtree import buildFolderTree
9
8
  from plone.app.layout.sitemap.sitemap import SiteMapView
10
- from plone.registry.interfaces import IRegistry
9
+ from plone.base.interfaces import IPloneSiteRoot
10
+ from plone.memoize import ram
11
+ from Products.CMFPlone.browser.navigation import CatalogSiteMap as BaseCatalogSiteMap
12
+ from Products.CMFPlone.browser.navtree import (
13
+ SitemapNavtreeStrategy as BaseSitemapNavtreeStrategy,
14
+ )
15
+ from Products.CMFPlone.browser.navtree import (
16
+ SitemapQueryBuilder as BaseSitemapQueryBuilder,
17
+ )
11
18
  from Products.CMFCore.utils import getToolByName
12
19
  from Products.CMFPlone.utils import normalizeString
13
- from zope.component import getUtility
20
+ from zope.component import getMultiAdapter
21
+ from zope.interface import implementer
22
+ from zope.interface import Interface
14
23
 
15
24
  import logging
25
+ import Missing
26
+ import time
16
27
 
17
28
  logger = logging.getLogger("imio.smartweb.core")
18
29
 
19
30
 
31
+ FRIENDLY_TYPES = [
32
+ "Collection",
33
+ "Image",
34
+ "Link",
35
+ "imio.smartweb.Folder",
36
+ "imio.smartweb.Page",
37
+ "imio.smartweb.PortalPage",
38
+ "imio.smartweb.Procedure",
39
+ "imio.smartweb.CirkwiView",
40
+ "imio.smartweb.DirectoryView",
41
+ "imio.smartweb.EventsView",
42
+ "imio.smartweb.NewsView",
43
+ ]
44
+
45
+
46
+ def cache_key(method, obj, request):
47
+ """We cache data from authentic sources for the sitemap (.xml.gz) for 2 hours."""
48
+ return f"sitemap_{obj.UID()}_{time.time() // 7200}"
49
+
50
+
51
+ @ram.cache(cache_key)
52
+ def get_endpoint_data(obj, request):
53
+ endpoint_mapping = {
54
+ "imio.smartweb.DirectoryView": DirectoryEndpointGet,
55
+ "imio.smartweb.EventsView": EventsEndpointGet,
56
+ "imio.smartweb.NewsView": NewsEndpointGet,
57
+ }
58
+ endpoint_class = endpoint_mapping.get(obj.portal_type)
59
+ if not endpoint_class:
60
+ return {}
61
+
62
+ endpoint = endpoint_class()
63
+ batch_size = 1000 if obj.portal_type == "imio.smartweb.DirectoryView" else 365
64
+ return (
65
+ endpoint.reply_for_given_object(
66
+ obj, request, fullobjects=0, batch_size=batch_size
67
+ )
68
+ or {}
69
+ )
70
+
71
+
72
+ def format_sitemap_items(items, base_url):
73
+ """Format items for sitemap(.xml.gz)"""
74
+ formatted_items = []
75
+ for item in items:
76
+ item_id = normalizeString(item.get("title"))
77
+ item_uid = item.get("id")
78
+ lastmod = item.get("modified") or "1970-01-01T00:00:00Z"
79
+ formatted_items.append(
80
+ {
81
+ "loc": f"{base_url}/{item_id}?u={item_uid}",
82
+ "lastmod": lastmod,
83
+ "Title": item.get("title"),
84
+ "Description": item.get("description", ""),
85
+ "getURL": f"{base_url}/{item_id}?u={item_uid}",
86
+ "getRemoteUrl": Missing.Value,
87
+ "currentItem": False,
88
+ "currentParent": False,
89
+ "normalized_review_state": "published",
90
+ "normalized_portal_type": "imio-smartweb-authsources-item",
91
+ }
92
+ )
93
+ return formatted_items
94
+
95
+
20
96
  class CustomSiteMapView(SiteMapView):
97
+ """Custom sitemap view. (get items for sitemap.xml.gz)"""
21
98
 
22
99
  def objects(self):
23
100
  """Returns the data to create the sitemap."""
24
-
25
- friendlytypes = [
26
- "Link",
27
- "imio.smartweb.Folder",
28
- "LIF",
29
- "LRF",
30
- "imio.smartweb.Page",
31
- "imio.smartweb.Procedure",
32
- "File",
33
- "Collection",
34
- "imio.smartweb.PortalPage",
35
- "Image",
36
- "imio.smartweb.CirkwiView",
37
- ]
101
+ friendlytypes = FRIENDLY_TYPES
38
102
 
39
103
  catalog = getToolByName(self.context, "portal_catalog")
40
- query = {}
41
- utils = getToolByName(self.context, "plone_utils")
42
- query["portal_type"] = utils.getUserFriendlyTypes(friendlytypes)
43
- registry = getUtility(IRegistry)
44
- typesUseViewActionInListings = frozenset(
45
- registry.get("plone.types_use_view_action_in_listings", [])
46
- )
104
+ query = {
105
+ "portal_type": getToolByName(
106
+ self.context, "plone_utils"
107
+ ).getUserFriendlyTypes(friendlytypes)
108
+ }
47
109
 
48
110
  is_plone_site_root = IPloneSiteRoot.providedBy(self.context)
49
111
  if not is_plone_site_root:
@@ -56,93 +118,83 @@ class CustomSiteMapView(SiteMapView):
56
118
  value = (item.modified.micros(), item.modified.ISO8601())
57
119
  default_page_modified[key] = value
58
120
 
59
- # The plone site root is not catalogued.
60
121
  if is_plone_site_root:
61
122
  loc = self.context.absolute_url()
62
123
  date = self.context.modified()
63
- # Comparison must be on GMT value
64
- modified = (date.micros(), date.ISO8601())
65
- default_modified = default_page_modified.get(loc, None)
66
- if default_modified is not None:
67
- modified = max(modified, default_modified)
68
- lastmod = modified[1]
69
- yield {
70
- "loc": loc,
71
- "lastmod": lastmod,
72
- # 'changefreq': 'always',
73
- # hourly/daily/weekly/monthly/yearly/never
74
- # 'prioriy': 0.5, # 0.0 to 1.0
75
- }
124
+ modified = max(
125
+ (date.micros(), date.ISO8601()), default_page_modified.get(loc, (0, ""))
126
+ )
127
+ yield {"loc": loc, "lastmod": modified[1]}
76
128
 
77
129
  query["is_default_page"] = False
78
130
  for item in catalog.searchResults(query):
79
131
  loc = item.getURL()
80
132
  date = item.modified
81
- # Comparison must be on GMT value
82
- modified = (date.micros(), date.ISO8601())
83
- default_modified = default_page_modified.get(loc, None)
84
- if default_modified is not None:
85
- modified = max(modified, default_modified)
86
- lastmod = modified[1]
87
- if item.portal_type in typesUseViewActionInListings:
88
- loc += "/view"
89
- yield {
90
- "loc": loc,
91
- "lastmod": lastmod,
92
- }
93
-
94
- auth_sources = {
95
- "directory": {
96
- "entity_reg_var": "smartweb.directory_entity_uid",
97
- "main_rest_view": "smartweb.default_directory_view",
98
- "vocabulary": "imio.smartweb.vocabulary.RemoteDirectoryEntities",
99
- "url": DIRECTORY_URL,
100
- },
101
- "events": {
102
- "entity_reg_var": "smartweb.events_entity_uid",
103
- "main_rest_view": "smartweb.default_events_view",
104
- "vocabulary": "imio.smartweb.vocabulary.RemoteEventsEntities",
105
- "url": EVENTS_URL,
106
- },
107
- "news": {
108
- "entity_reg_var": "smartweb.news_entity_uid",
109
- "main_rest_view": "smartweb.default_news_view",
110
- "vocabulary": "imio.smartweb.vocabulary.RemoteNewsEntities",
111
- "url": NEWS_URL,
112
- },
113
- }
114
- for auth_source_key, auth_source_value in auth_sources.items():
115
- entity_uid = api.portal.get_registry_record(
116
- auth_source_value.get("entity_reg_var")
117
- )
118
- if entity_uid is None:
119
- return self.request.response.setStatus(404, "No entity found")
120
- entity_id = get_entity_id(auth_source_value.get("vocabulary"), entity_uid)
121
- auth_source_uid = api.portal.get_registry_record(
122
- f"smartweb.default_{auth_source_key}_view", default=None
133
+ modified = max(
134
+ (date.micros(), date.ISO8601()), default_page_modified.get(loc, (0, ""))
123
135
  )
124
- if auth_source_uid is None:
125
- return self.request.response.setStatus(
126
- 404, "No default authentic source found"
127
- )
128
- obj = api.content.get(UID=auth_source_uid)
129
- if obj is None:
130
- logger.warning(
131
- f"Seems that a main authentic view (for {auth_source_key}) is not found"
132
- )
133
- continue
134
- auth_source_view_url = obj.absolute_url()
135
- results = get_auth_sources_response(
136
- auth_source_key, normalizeString(entity_id), (60 * 60 * 24)
137
- ).json()
138
- if results is None or results.get("type") == "ValueError":
136
+ yield {"loc": loc, "lastmod": modified[1]}
137
+
138
+ brains = catalog(
139
+ portal_type=[
140
+ "imio.smartweb.EventsView",
141
+ "imio.smartweb.NewsView",
142
+ "imio.smartweb.DirectoryView",
143
+ ]
144
+ )
145
+ for brain in brains:
146
+ obj = brain.getObject()
147
+ data = get_endpoint_data(obj, obj.REQUEST)
148
+ yield from format_sitemap_items(data.get("items", {}), obj.absolute_url())
149
+
150
+
151
+ @implementer(IImioSmartwebCoreLayer)
152
+ class CatalogSiteMap(BaseCatalogSiteMap):
153
+ def siteMap(self):
154
+ context = aq_inner(self.context)
155
+
156
+ queryBuilder = SitemapQueryBuilder(context)
157
+ query = queryBuilder()
158
+ strategy = getMultiAdapter((context, self), ISmartwebNavtreeStrategy)
159
+ base_folder_tree = buildFolderTree(
160
+ context, obj=context, query=query, strategy=strategy
161
+ )
162
+
163
+ for child in base_folder_tree.get("children"):
164
+ obj = child.get("item").getObject()
165
+ data = get_endpoint_data(obj, obj.REQUEST)
166
+ if not data:
139
167
  continue
140
- for item in results.get("items"):
141
- item_id = normalizeString(item.get("title"))
142
- item_uid = item.get("id")
143
- loc = f"{auth_source_view_url}/{item_id}?u={item_uid}"
144
- lastmod = item.get("modified")
145
- yield {
146
- "loc": loc,
147
- "lastmod": lastmod,
148
- }
168
+ child["children"] = format_sitemap_items(
169
+ data.get("items", []), obj.absolute_url()
170
+ )
171
+
172
+ return base_folder_tree
173
+
174
+
175
+ class SitemapQueryBuilder(BaseSitemapQueryBuilder):
176
+ """Builds the request for the sitemap with custom content types."""
177
+
178
+ def __init__(self, context):
179
+ self.context = context
180
+
181
+ def __call__(self):
182
+ query = {}
183
+ custom_query = getattr(self.context, "getCustomNavQuery", None)
184
+ if custom_query and callable(custom_query):
185
+ query = custom_query()
186
+ query["portal_type"] = FRIENDLY_TYPES
187
+ query["review_state"] = "published"
188
+ return query
189
+
190
+
191
+ class ISmartwebNavtreeStrategy(Interface):
192
+ """Marker interface for the smartweb navtree strategy."""
193
+
194
+
195
+ @implementer(ISmartwebNavtreeStrategy)
196
+ class SitemapNavtreeStrategy(BaseSitemapNavtreeStrategy):
197
+
198
+ def nodeFilter(self, node):
199
+ # Return all nodes in sitemap even if they are exclude from nav
200
+ return True
@@ -15,9 +15,12 @@ class BaseEndpoint(object):
15
15
  language = "fr"
16
16
  remote_endpoint = ""
17
17
 
18
- def __init__(self, context, request):
18
+ def __init__(self, context, request, fullobjects=1, batch_size=0):
19
+
19
20
  self.context = context
20
21
  self.request = request
22
+ self.fullobjects = fullobjects
23
+ self.batch_size = batch_size
21
24
 
22
25
  def __call__(self):
23
26
  results = get_json(self.query_url, timeout=20)
@@ -46,7 +49,7 @@ class BaseEndpoint(object):
46
49
  item[f"{field}_full_scale"] = (
47
50
  f"{item['@id']}/@@images/{field}/?cache_key={modified_hash}"
48
51
  )
49
- del item[field]
52
+ item.pop(field, None)
50
53
 
51
54
  def construct_query_string(self, params):
52
55
  params = "&".join(params)
@@ -107,7 +107,7 @@ class CampaignEndpoint(BaseTsEndpoint):
107
107
  extra_params = [f"{k}={v}" for k, v in self.request.form.items()]
108
108
  extra_params = "&".join(extra_params)
109
109
  campaign_id = self.context.linked_campaign
110
- url = f"{wcs_api}/cards/imio-ideabox-projet/list?campagne={campaign_id}&full=on&filter-statut=Vote|Enregistr%C3%A9e&filter-statut-operator=in&{extra_params}"
110
+ url = f"{wcs_api}/cards/imio-ideabox-projet/list?filter-campagne={campaign_id}&full=on&filter-statut=Vote|Enregistr%C3%A9e&filter-statut-operator=in&{extra_params}"
111
111
  return url
112
112
 
113
113
 
@@ -124,7 +124,7 @@ class ZonesEndpoint(BaseTsEndpoint):
124
124
  def query_url(self):
125
125
  wcs_api = get_ts_api_url("wcs")
126
126
  campaign_id = self.context.linked_campaign
127
- return f"{wcs_api}/cards/imio-ideabox-zone/list?campagne={campaign_id}"
127
+ return f"{wcs_api}/cards/imio-ideabox-zone/list?filter-campagne={campaign_id}"
128
128
 
129
129
 
130
130
  @implementer(IExpandableElement)
@@ -140,7 +140,7 @@ class TsTopicsEndpoint(BaseTsEndpoint):
140
140
  def query_url(self):
141
141
  wcs_api = get_ts_api_url("wcs")
142
142
  campaign_id = self.context.linked_campaign
143
- return f"{wcs_api}/cards/imio-ideabox-theme/list?campagne={campaign_id}"
143
+ return f"{wcs_api}/cards/imio-ideabox-theme/list?filter-campagne={campaign_id}"
144
144
 
145
145
 
146
146
  @implementer(IExpandableElement)
@@ -13,7 +13,7 @@ from zope.interface import Interface
13
13
 
14
14
  class BaseDirectoryEndpoint(BaseEndpoint):
15
15
  def __call__(self):
16
- results = super(BaseDirectoryEndpoint, self).__call__()
16
+ results = super(BaseDirectoryEndpoint, self).__call__() or {}
17
17
  if not results.get("items"):
18
18
  return results
19
19
  orientation = self.context.orientation
@@ -45,10 +45,13 @@ class BaseDirectoryEndpoint(BaseEndpoint):
45
45
  "metadata_fields=taxonomy_contact_category",
46
46
  "metadata_fields=topics",
47
47
  "metadata_fields=has_leadimage",
48
- "fullobjects=1",
48
+ "fullobjects={}".format(self.fullobjects),
49
49
  "sort_on=sortable_title",
50
- "b_size={}".format(self.context.nb_results),
51
50
  ]
51
+ if self.batch_size == 0:
52
+ params.append("b_size={}".format(self.context.nb_results))
53
+ else:
54
+ params.append("b_size={}".format(self.batch_size))
52
55
  if self.context.selected_categories is not None:
53
56
  for category in self.context.selected_categories:
54
57
  params.append(f"taxonomy_contact_category.query={category}")
@@ -74,6 +77,11 @@ class DirectoryEndpointGet(BaseService):
74
77
  def reply(self):
75
78
  return DirectoryEndpoint(self.context, self.request)()
76
79
 
80
+ def reply_for_given_object(self, obj, request, fullobjects=1, batch_size=0):
81
+ return DirectoryEndpoint(
82
+ obj, request, fullobjects=fullobjects, batch_size=batch_size
83
+ )()
84
+
77
85
 
78
86
  class DirectoryFiltersEndpointGet(BaseService):
79
87
  def reply(self):
@@ -12,7 +12,7 @@ from zope.interface import Interface
12
12
 
13
13
  class BaseEventsEndpoint(BaseEndpoint):
14
14
  def __call__(self):
15
- results = super(BaseEventsEndpoint, self).__call__()
15
+ results = super(BaseEventsEndpoint, self).__call__() or {}
16
16
  if not results or not results.get("items"):
17
17
  return results
18
18
  orientation = self.context.orientation
@@ -43,9 +43,12 @@ class BaseEventsEndpoint(BaseEndpoint):
43
43
  "metadata_fields=has_leadimage",
44
44
  "metadata_fields=UID",
45
45
  "sort_on=event_dates",
46
- "fullobjects=1",
47
- "b_size={}".format(self.context.nb_results),
46
+ "fullobjects={}".format(self.fullobjects),
48
47
  ]
48
+ if self.batch_size == 0:
49
+ params.append("b_size={}".format(self.context.nb_results))
50
+ else:
51
+ params.append("b_size={}".format(self.batch_size))
49
52
  if self.context.selected_event_types is not None:
50
53
  for event_type in self.context.selected_event_types:
51
54
  params.append(f"event_type={event_type}")
@@ -70,6 +73,11 @@ class EventsEndpointGet(BaseService):
70
73
  def reply(self):
71
74
  return EventsEndpoint(self.context, self.request)()
72
75
 
76
+ def reply_for_given_object(self, obj, request, fullobjects=1, batch_size=0):
77
+ return EventsEndpoint(
78
+ obj, request, fullobjects=fullobjects, batch_size=batch_size
79
+ )()
80
+
73
81
 
74
82
  class EventsFiltersEndpointGet(BaseService):
75
83
  def reply(self):
@@ -12,7 +12,7 @@ from zope.interface import Interface
12
12
 
13
13
  class BaseNewsEndpoint(BaseEndpoint):
14
14
  def __call__(self):
15
- results = super(BaseNewsEndpoint, self).__call__()
15
+ results = super(BaseNewsEndpoint, self).__call__() or {}
16
16
  if not results.get("items"):
17
17
  return results
18
18
  orientation = self.context.orientation
@@ -46,9 +46,12 @@ class BaseNewsEndpoint(BaseEndpoint):
46
46
  "metadata_fields=UID",
47
47
  "sort_on=effective",
48
48
  "sort_order=descending",
49
- "b_size={}".format(self.context.nb_results),
50
- "fullobjects=1",
49
+ "fullobjects={}".format(self.fullobjects),
51
50
  ]
51
+ if self.batch_size == 0:
52
+ params.append("b_size={}".format(self.context.nb_results))
53
+ else:
54
+ params.append("b_size={}".format(self.batch_size))
52
55
  params = self.construct_query_string(params)
53
56
  url = f"{NEWS_URL}/{self.remote_endpoint}?{params}"
54
57
  return url
@@ -70,6 +73,11 @@ class NewsEndpointGet(BaseService):
70
73
  def reply(self):
71
74
  return NewsEndpoint(self.context, self.request)()
72
75
 
76
+ def reply_for_given_object(self, obj, request, fullobjects=1, batch_size=0):
77
+ return NewsEndpoint(
78
+ obj, request, fullobjects=fullobjects, batch_size=batch_size
79
+ )()
80
+
73
81
 
74
82
  class NewsFiltersEndpointGet(BaseService):
75
83
  def reply(self):
@@ -57,7 +57,10 @@ def get_default_view_url(view_type):
57
57
  if not record:
58
58
  return ""
59
59
  with api.env.adopt_user(username="admin"):
60
- return api.content.get(UID=record).absolute_url()
60
+ obj = api.content.get(UID=record)
61
+ if not obj:
62
+ return ""
63
+ return obj.absolute_url()
61
64
 
62
65
 
63
66
  def get_views_mapping(navigation_root):
@@ -69,10 +69,10 @@ def get_auth_sources_response(
69
69
  "v": wf_status,
70
70
  },
71
71
  ],
72
- "metadata_fields": ["title", "modified"],
72
+ "metadata_fields": ["title", "modified", "id"],
73
73
  "sort_on": "effective",
74
74
  "sort_order": "descending",
75
- "fullobjects": True,
75
+ "fullobjects": False,
76
76
  "b_start": 0,
77
77
  "b_size": 4000,
78
78
  }
@@ -81,7 +81,7 @@ def get_auth_sources_response(
81
81
  auth = get_wca_token(client_id, client_secret)
82
82
  headers = {
83
83
  "Accept": "application/json",
84
- "Content-Type": "text/plain",
84
+ "Content-Type": "application/json",
85
85
  "Authorization": auth,
86
86
  }
87
87
  url = f"{url}/@querystring-search"
@@ -5,6 +5,7 @@ from collective.messagesviewlet.interfaces import ICollectiveMessagesviewletLaye
5
5
  from collective.solr.browser.interfaces import IThemeSpecific
6
6
  from imio.smartweb.common.interfaces import IImioSmartwebCommonLayer
7
7
  from plone.app.contenttypes.interfaces import IPloneAppContenttypesLayer
8
+ from Products.CMFPlone.browser.interfaces import ISiteMap
8
9
  from zope.interface import Interface
9
10
 
10
11
 
@@ -22,6 +23,7 @@ class IImioSmartwebCoreLayer(
22
23
  ILayerSpecific,
23
24
  IThemeSpecific,
24
25
  ICollectiveMessagesviewletLayer,
26
+ ISiteMap,
25
27
  ):
26
28
  """Marker interface that defines a browser layer."""
27
29
 
@@ -1,6 +1,6 @@
1
1
  <?xml version='1.0' encoding='UTF-8'?>
2
2
  <metadata>
3
- <version>1066</version>
3
+ <version>1067</version>
4
4
  <dependencies>
5
5
  <dependency>profile-plone.app.dexterity:default</dependency>
6
6
  <dependency>profile-plone.app.imagecropping:default</dependency>
@@ -32,4 +32,8 @@
32
32
  <element value="imio.smartweb.orientation"/>
33
33
  </property>
34
34
 
35
+ <property name="default_view">table_view</property>
36
+ <property name="default_view_fallback">False</property>
37
+ <property name="immediate_view">view</property>
38
+
35
39
  </object>
@@ -27,5 +27,6 @@
27
27
  <object meta_type="Dexterity FTI" name="imio.smartweb.SectionSendinblue"/>
28
28
  <object meta_type="Dexterity FTI" name="imio.smartweb.SectionSlide"/>
29
29
  <object meta_type="Dexterity FTI" name="imio.smartweb.SectionText"/>
30
+ <object meta_type="Dexterity FTI" name="imio.smartweb.SectionTimestampedPublications"/>
30
31
  <object meta_type="Dexterity FTI" name="imio.smartweb.SectionVideo"/>
31
32
  </object>
@@ -19,6 +19,7 @@
19
19
  <type type_id="imio.smartweb.SectionSlide"/>
20
20
  <type type_id="imio.smartweb.SectionNews"/>
21
21
  <type type_id="imio.smartweb.SectionText"/>
22
+ <type type_id="imio.smartweb.SectionTimestampedPublications"/>
22
23
  <type type_id="imio.smartweb.SectionVideo"/>
23
24
  </bindings>
24
25
  </object>
@@ -85,6 +85,38 @@ class TestIdeabox(ImioSmartwebTestCase):
85
85
  self.assertEqual(campaign_view.title, "Sprint iMio Fall 2024")
86
86
  self.assertEqual(campaign_view.id, "sprint-imio-fall-2024")
87
87
 
88
+ # CampaignNameChooser : Give th Title to our object thanks to e-guichet object Title.
89
+ @patch("imio.smartweb.core.subscribers.get_basic_auth_json")
90
+ @patch("imio.smartweb.core.subscribers.get_value_from_registry")
91
+ @patch("imio.smartweb.core.contents.rest.campaign.content.get_basic_auth_json")
92
+ @patch("imio.smartweb.core.contents.rest.campaign.content.get_value_from_registry")
93
+ def test_campaign_name_chooser(
94
+ self,
95
+ m_get_value_from_registry_namechooser,
96
+ m_get_basic_auth_json_namechooser,
97
+ m_get_value_from_registry,
98
+ m_get_basic_auth_json,
99
+ ):
100
+ from imio.smartweb.core.contents.rest.campaign.content import CampaignView
101
+ from zope.component import queryAdapter
102
+ from zope.container.interfaces import INameChooser
103
+
104
+ m_get_value_from_registry.return_value = (
105
+ "https://staging3-formulaires.guichet-citoyen.be/api"
106
+ )
107
+ m_get_basic_auth_json.return_value = self.json_campaign_raw_mock
108
+
109
+ m_get_value_from_registry_namechooser.return_value = (
110
+ "https://staging3-formulaires.guichet-citoyen.be/api"
111
+ )
112
+ m_get_basic_auth_json_namechooser.return_value = self.json_campaign_raw_mock
113
+ campaign_view = CampaignView(id="test-campaign", title=None)
114
+ campaign_view.linked_campaign = "2"
115
+ name_chooser = queryAdapter(self.folder, INameChooser)
116
+ generated_id = name_chooser.chooseName(None, campaign_view)
117
+ self.assertEqual(campaign_view.title, "Sprint iMio Fall 2024")
118
+ self.assertEqual(generated_id, "sprint-imio-fall-2024")
119
+
88
120
  @requests_mock.Mocker()
89
121
  @patch("imio.smartweb.core.subscribers.get_basic_auth_json")
90
122
  @patch("imio.smartweb.core.subscribers.get_value_from_registry")
@@ -110,7 +142,7 @@ class TestIdeabox(ImioSmartwebTestCase):
110
142
  url = endpoint.query_url
111
143
  self.assertEqual(
112
144
  url,
113
- "https://demo-formulaires.guichet-citoyen.be/api/cards/imio-ideabox-projet/list?campagne=2&full=on&filter-statut=Vote|Enregistr%C3%A9e&filter-statut-operator=in&",
145
+ "https://demo-formulaires.guichet-citoyen.be/api/cards/imio-ideabox-projet/list?filter-campagne=2&full=on&filter-statut=Vote|Enregistr%C3%A9e&filter-statut-operator=in&",
114
146
  )
115
147
 
116
148
  m.get(url, text=json.dumps({}))