Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Changelog

- Avoid an error if category root folder does not exist
[mpeeters]
- Added error if frontend and backend data differ when setting values.
[chris-adam]


0.70 (2025-11-05)
Expand Down
52 changes: 37 additions & 15 deletions src/collective/iconifiedcategory/browser/actionview.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,35 @@ def __call__(self):
2 --> error;
"""
writer = getUtility(IJSONWriter)
values = {'msg': u'Values have been set'}
results = {'msg': u'Values have been set'}
try:
self.request.response.setHeader('content-type',
'application/json')
status, msg = self.set_values(self.get_values())
values['status'] = status
values = self.get_values()
current_values = self.get_current_values()
for k, v in values.items():
if v != current_values.get(k):
results["reload"] = True
api.portal.show_message(
_("Your action could not be performed because the content has been edited by another user."),
request=self.request,
type="error",
)
raise ValueError
status, msg = self.set_values(values)

# return current values to update HTML data-* attributes
current_values = self.get_current_values()
for k, v in values.items():
results["data-%s" % k] = current_values.get(k)

results['status'] = status
if msg:
values['msg'] = self._translate(msg)
results['msg'] = self._translate(msg)
except Exception:
values['status'] = 2
values['msg'] = self._translate(_('Error during process'))
return writer.write(values)
results['status'] = 2
results['msg'] = self._translate(_('Error during process'))
return writer.write(results)

def get_current_values(self):
return {k: getattr(self.context, k)
Expand All @@ -68,7 +85,7 @@ def get_values(self):
return {k: self.convert_boolean(self.request.get(v))
for k, v in self.attribute_mapping.items()}

def _may_set_values(self, values, ):
def _may_set_values(self, values):
res = bool(api.user.has_permission(self.permission, obj=self.context))
if res:
# is this functionnality enabled?
Expand All @@ -88,6 +105,11 @@ def set_values(self, values):

old_values = self.get_current_values()

# If the view doesn't define _get_next_values, toggle the boolean value
if not hasattr(self, '_get_next_values'):
for key in values.keys():
values[key] = not old_values[key]

for key, value in values.items():
self._set_value(key, value)
status, msg = self._get_status(values), utils.boolean_message(
Expand Down Expand Up @@ -131,7 +153,7 @@ def convert_boolean(value):

class ToPrintChangeView(BaseView):
attribute_mapping = {
'to_print': 'iconified-value',
'to_print': 'to_print',
}
category_group_attr_name = 'to_be_printed_activated'
attr_name = 'to_print'
Expand All @@ -145,7 +167,7 @@ def set_values(self, values):

class ConfidentialChangeView(BaseView):
attribute_mapping = {
'confidential': 'iconified-value',
'confidential': 'confidential',
}
category_group_attr_name = 'confidentiality_activated'
attr_name = 'confidential'
Expand All @@ -154,8 +176,8 @@ class ConfidentialChangeView(BaseView):

class SignedChangeView(BaseView):
attribute_mapping = {
'signed': 'iconified-value',
'to_sign': 'iconified-value',
'signed': 'signed',
'to_sign': 'to_sign',
}
category_group_attr_name = 'signed_activated'
attr_name = 'to_sign'
Expand Down Expand Up @@ -193,8 +215,8 @@ def set_values(self, values):

class ApprovedChangeView(BaseView):
attribute_mapping = {
'approved': 'iconified-value',
'to_approve': 'iconified-value',
'approved': 'approved',
'to_approve': 'to_approve',
}
category_group_attr_name = 'approved_activated'
attr_name = 'to_approve'
Expand Down Expand Up @@ -232,7 +254,7 @@ def set_values(self, values):

class PublishableChangeView(BaseView):
attribute_mapping = {
'publishable': 'iconified-value',
'publishable': 'publishable',
}
category_group_attr_name = 'publishable_activated'
attr_name = 'publishable'
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ jQuery(function($) {
//});

$('a.iconified-action').click(function() {
var obj = $(this);
let obj = $(this);
if (!obj.hasClass('editable')) {
return false;
}
var values = {'iconified-value': !obj.hasClass('active')};
let values = obj.data();
$.getJSON(
obj.attr('href'),
values,
Expand All @@ -85,6 +85,17 @@ jQuery(function($) {
}
obj.attr('alt', data.msg);
obj.attr('title', data.msg);

// Update all data-* attributes from the response
for (let key in data) {
if (key.startsWith('data-')) {
// Extract the data key name (remove 'data-' prefix)
let dataKey = key.substring(5);
// Update both the attribute and the data cache
obj.attr(key, data[key]);
obj.data(dataKey, data[key]);
}
}
}
);
return false;
Expand Down
33 changes: 32 additions & 1 deletion src/collective/iconifiedcategory/browser/tabview.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,13 +322,17 @@ def css_class(self, content):
return '{0} editable'.format(base_css)
return base_css

def render_data(self, content):
return u''

def renderCell(self, content):
link = (u'<a href="{0}" class="iconified-action{1}" alt="{2}" '
u'title="{2}"></a>')
u'title="{2}" {3}></a>')
return link.format(
self.get_url(content),
self.css_class(content),
self.alt(content),
self.render_data(content),
)


Expand All @@ -346,6 +350,11 @@ def alt(self, content):
context=self.table.request,
)

def render_data(self, content):
return u'data-to_print="{0}"'.format(
content.to_print and 'true' or 'false'
)


class ConfidentialColumn(IconClickableColumn):
header = _(u'Confidential')
Expand All @@ -361,6 +370,11 @@ def alt(self, content):
context=self.table.request,
)

def render_data(self, content):
return u'data-confidential="{0}"'.format(
content.confidential and 'true' or 'false'
)


class SignedColumn(IconClickableColumn):
header = _(u'Signed')
Expand All @@ -383,6 +397,12 @@ def _deactivated_is_useable(self):
def is_deactivated(self, content):
return not getattr(content, 'to_sign', True)

def render_data(self, content):
return u'data-to_sign="{0}" data-signed="{1}"'.format(
content.to_sign and 'true' or 'false',
content.signed and 'true' or 'false'
)


class ApprovedColumn(IconClickableColumn):
header = _(u'Approved')
Expand All @@ -405,6 +425,12 @@ def _deactivated_is_useable(self):
def is_deactivated(self, content):
return not getattr(content, 'to_approve', True)

def render_data(self, content):
return u'data-to_approve="{0}" data-approved="{1}"'.format(
content.to_approve and 'true' or 'false',
content.approved and 'true' or 'false'
)


class PublishableColumn(IconClickableColumn):
header = _(u'Publishable')
Expand All @@ -420,6 +446,11 @@ def alt(self, content):
context=self.table.request,
)

def render_data(self, content):
return u'data-publishable="{0}"'.format(
content.publishable and 'true' or 'false'
)


class ActionColumn(BaseColumn):
header = u''
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2025-10-08 08:22+0000\n"
"POT-Creation-Date: 2025-12-01 15:43+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
Expand Down Expand Up @@ -336,6 +336,10 @@ msgstr ""
msgid "You must select a PDF file!"
msgstr ""

#: ../browser/actionview.py:59
msgid "Your action could not be performed because the content has been edited by another user."
msgstr ""

#: ../configure.zcml:32
msgid "collective.iconifiedcategory"
msgstr ""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2025-10-08 08:22+0000\n"
"POT-Creation-Date: 2025-12-01 15:43+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
Expand Down Expand Up @@ -340,6 +340,10 @@ msgstr "oui et limiter l'accès au téléchargement"
msgid "You must select a PDF file!"
msgstr "La catégorie sélectionnée requiert l'utilisation d'un fichier au format PDF uniquement!"

#: ../browser/actionview.py:59
msgid "Your action could not be performed because the content has been edited by another user."
msgstr "L'action n'a pas pu être effectuée car un autre utilisateur a modifié l'élément."

#: ../configure.zcml:32
msgid "collective.iconifiedcategory"
msgstr ""
Expand Down
10 changes: 5 additions & 5 deletions src/collective/iconifiedcategory/tests/test_actionview.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,20 @@ def test__call__(self):
self.assertEqual(result[u'msg'], u'No values to set')

# only doable if user has Modify portal content on obj
view.attribute_mapping = {'title': 'action-value-title'}
self.portal.REQUEST.set('action-value-title', 'My new title')
view.attribute_mapping = {'approved': 'approved'}
self.portal.REQUEST.set('approved', 'false')
obj.manage_permission(ModifyPortalContent, roles=[])
result = reader.read(view())
self.assertEqual(result[u'status'], 2)
self.assertEqual(result[u'msg'], u'Error during process')
obj.manage_permission(ModifyPortalContent, roles=['Manager'])

# change title
self.assertEqual(obj.title, u'file.txt')
self.assertFalse(obj.approved)
result = reader.read(view())
self.assertEqual(result[u'status'], -1)
self.assertEqual(result[u'status'], 1)
self.assertEqual(result[u'msg'], u'Values have been set')
self.assertEqual(obj.title, 'My new title')
self.assertTrue(obj.approved)

def test_get_current_values(self):
obj = self.portal['file_txt']
Expand Down
10 changes: 6 additions & 4 deletions src/collective/iconifiedcategory/tests/test_tabview.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ def test_PrintColumn(self):
u'<a href="#" '
u'class="iconified-action deactivated" '
u'alt="Not convertible to a printable format" '
u'title="Not convertible to a printable format"></a>')
u'title="Not convertible to a printable format" '
u'data-to_print="false"></a>')
self.assertIsNone(categorized_content.to_print)
self.assertIsNone(obj.to_print)

Expand All @@ -130,7 +131,7 @@ def test_PrintColumn(self):
u'<a href="http://nohost/plone/file_txt/@@iconified-print" '
u'class="iconified-action editable" '
u'alt="Should not be printed" '
u'title="Should not be printed"></a>')
u'title="Should not be printed" data-to_print="false"></a>')

# set to_print to True
obj.to_print = True
Expand All @@ -145,12 +146,13 @@ def test_PrintColumn(self):
u'<a href="http://nohost/plone/file_txt/@@iconified-print" '
u'class="iconified-action active editable" '
u'alt="Must be printed" '
u'title="Must be printed"></a>')
u'title="Must be printed" data-to_print="true"></a>')

# if element is not editable, the 'editable' CSS class is not there
obj.manage_permission(ModifyPortalContent, roles=[])
notify(ObjectModifiedEvent(obj))
self.assertEqual(column.renderCell(categorized_content),
u'<a href="http://nohost/plone/file_txt/@@iconified-print" '
u'class="iconified-action active" '
u'alt="Must be printed" title="Must be printed"></a>')
u'alt="Must be printed" title="Must be printed" '
u'data-to_print="true"></a>')