-
Notifications
You must be signed in to change notification settings - Fork 1
Add duplicate action for outgoing mails #33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5e2b45c
7961e9b
23fb7de
52c2faf
eb42d2e
815dbc5
e3b7504
16203e0
5a77836
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| <td class="noPadding" i18n:domain="imio.dms.mail"> | ||
| <a tal:attributes="href string:${context/absolute_url}/@@om-duplicate" | ||
| target="_parent" | ||
| class="overlay" | ||
| tal:define="dummy view/saveHasActions;"> | ||
| <input tal:condition="not:view/useIcons" type="button" value="Duplicate" class="apButton apButtonAction apButtonAction_duplicate" i18n:attributes="value" /> | ||
| <img tal:condition="view/useIcons" i18n:attributes="title" title="Duplicate outgoing mail" tal:attributes="src string: ${view/portal_url}/++resource++imio.dms.mail/copy.svg;"/> | ||
| </a> | ||
| </td> | ||
| <td class="noPadding"> |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,6 +7,8 @@ | |||||||||||||||||||||||||||||||||
| from imio.dms.mail import _ | ||||||||||||||||||||||||||||||||||
| from imio.dms.mail import _tr | ||||||||||||||||||||||||||||||||||
| from imio.dms.mail import PMH_ENABLED | ||||||||||||||||||||||||||||||||||
| from imio.dms.mail.browser.settings import IImioDmsMailConfig | ||||||||||||||||||||||||||||||||||
| from imio.dms.mail.browser.settings import omail_duplicate_fields | ||||||||||||||||||||||||||||||||||
| from imio.dms.mail.browser.table import ApprovalTable | ||||||||||||||||||||||||||||||||||
| from imio.dms.mail.browser.table import CKTemplatesTable | ||||||||||||||||||||||||||||||||||
| from imio.dms.mail.browser.table import PersonnelTable | ||||||||||||||||||||||||||||||||||
|
|
@@ -30,6 +32,7 @@ | |||||||||||||||||||||||||||||||||
| from imio.helpers.fancytree.views import BaseRenderFancyTree | ||||||||||||||||||||||||||||||||||
| from imio.helpers.workflow import do_transitions | ||||||||||||||||||||||||||||||||||
| from imio.helpers.xhtml import object_link | ||||||||||||||||||||||||||||||||||
| from imio.pyutils.utils import safe_encode | ||||||||||||||||||||||||||||||||||
| from plone import api | ||||||||||||||||||||||||||||||||||
| from Products.CMFCore.utils import getToolByName | ||||||||||||||||||||||||||||||||||
| from Products.CMFPlone import utils | ||||||||||||||||||||||||||||||||||
|
|
@@ -42,6 +45,10 @@ | |||||||||||||||||||||||||||||||||
| from Products.PageTemplates.Expressions import SecureModuleImporter | ||||||||||||||||||||||||||||||||||
| from Products.statusmessages.interfaces import IStatusMessage | ||||||||||||||||||||||||||||||||||
| from unidecode import unidecode # unidecode_expect_nonascii not yet available in used version | ||||||||||||||||||||||||||||||||||
| from z3c.form import button | ||||||||||||||||||||||||||||||||||
| from z3c.form.field import Fields | ||||||||||||||||||||||||||||||||||
| from z3c.form.form import Form | ||||||||||||||||||||||||||||||||||
| from zope import schema | ||||||||||||||||||||||||||||||||||
| from zope.annotation import IAnnotations | ||||||||||||||||||||||||||||||||||
| from zope.component import getMultiAdapter | ||||||||||||||||||||||||||||||||||
| from zope.i18n import translate | ||||||||||||||||||||||||||||||||||
|
|
@@ -88,6 +95,65 @@ def redirect_url(self, uid): | |||||||||||||||||||||||||||||||||
| return "{}/persistent-document-generation?{}".format(url, "&".join(params)) | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| class OMDuplicateForm(Form): | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| """Duplicate an outgoing mail.""" | ||||||||||||||||||||||||||||||||||
| label = _(u"Duplicate mail") | ||||||||||||||||||||||||||||||||||
| ignoreContext = True | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| def update(self): | ||||||||||||||||||||||||||||||||||
| """Handle fields.""" | ||||||||||||||||||||||||||||||||||
| om_fields = api.portal.get_registry_record("omail_fields", IImioDmsMailConfig, []) | ||||||||||||||||||||||||||||||||||
| om_fields = [dic["field_name"] for dic in om_fields] | ||||||||||||||||||||||||||||||||||
| matching = {u"category": "IClassificationFolder.classification_categories", | ||||||||||||||||||||||||||||||||||
| u"folder": "IClassificationFolder.classification_folders", | ||||||||||||||||||||||||||||||||||
| u"reply_to": "reply_to", | ||||||||||||||||||||||||||||||||||
| u"link_to_duplicated": "reply_to"} | ||||||||||||||||||||||||||||||||||
| to_show = api.portal.get_registry_record("omail_duplicate_display_fields", IImioDmsMailConfig, []) | ||||||||||||||||||||||||||||||||||
| to_true = api.portal.get_registry_record("omail_duplicate_true_default_values", IImioDmsMailConfig, []) | ||||||||||||||||||||||||||||||||||
| for term in omail_duplicate_fields: | ||||||||||||||||||||||||||||||||||
| if ((term.value in matching and matching[term.value] not in om_fields) or | ||||||||||||||||||||||||||||||||||
| (term.value not in to_show and term.value not in to_true)): | ||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||
| self.fields += Fields( | ||||||||||||||||||||||||||||||||||
| schema.Bool( | ||||||||||||||||||||||||||||||||||
| __name__=safe_encode(term.value), | ||||||||||||||||||||||||||||||||||
| title=term.title, | ||||||||||||||||||||||||||||||||||
| default=term.value in to_true, | ||||||||||||||||||||||||||||||||||
| )) | ||||||||||||||||||||||||||||||||||
| super(OMDuplicateForm, self).update() | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| def updateWidgets(self, prefix=None): | ||||||||||||||||||||||||||||||||||
| super(OMDuplicateForm, self).updateWidgets() | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| # we hide fields not to show | ||||||||||||||||||||||||||||||||||
| to_show = api.portal.get_registry_record("omail_duplicate_display_fields", IImioDmsMailConfig, []) | ||||||||||||||||||||||||||||||||||
| for term in omail_duplicate_fields: | ||||||||||||||||||||||||||||||||||
| if term.value not in to_show: | ||||||||||||||||||||||||||||||||||
| self.widgets[term.value].mode = "hidden" | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+126
to
+133
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In 🐛 Proposed fix to check widget existence before accessing def updateWidgets(self, prefix=None):
super(OMDuplicateForm, self).updateWidgets()
# we hide fields not to show
to_show = api.portal.get_registry_record("omail_duplicate_display_fields", IImioDmsMailConfig, [])
for term in omail_duplicate_fields:
- if term.value not in to_show:
+ if term.value not in to_show and term.value in self.widgets:
self.widgets[term.value].mode = "hidden"📝 Committable suggestion
Suggested change
🧰 Tools🪛 Ruff (0.15.2)[warning] 126-126: Unused method argument: (ARG002) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| @button.buttonAndHandler(_('Duplicate'), name='duplicate') | ||||||||||||||||||||||||||||||||||
| def handleApply(self, action): | ||||||||||||||||||||||||||||||||||
| data, errors = self.extractData() | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if errors: | ||||||||||||||||||||||||||||||||||
| self.status = self.formErrorsMessage | ||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| # Duplicate the mail | ||||||||||||||||||||||||||||||||||
| odm_utils = getMultiAdapter((self.context, self.request), name="odm-utils") | ||||||||||||||||||||||||||||||||||
| duplicated_mail = odm_utils.duplicate( | ||||||||||||||||||||||||||||||||||
| keep_category=data.get('category', False), | ||||||||||||||||||||||||||||||||||
| keep_folder=data.get('folder', False), | ||||||||||||||||||||||||||||||||||
| keep_reply_to=data.get('reply_to', False), | ||||||||||||||||||||||||||||||||||
| keep_dms_files=data.get('dms_files', False), | ||||||||||||||||||||||||||||||||||
| keep_annexes=data.get('annexes', False), | ||||||||||||||||||||||||||||||||||
| link_to_duplicated=data.get('link_to_duplicated', False), | ||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| self.request.response.redirect(duplicated_mail.absolute_url()+"/edit") | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| def parse_query(text): | ||||||||||||||||||||||||||||||||||
| """Copied from plone.app.vocabularies.catalog.parse_query but cleaned.""" | ||||||||||||||||||||||||||||||||||
| for char in "?-+*()": | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -567,6 +567,16 @@ def dmsoutgoingmail_added(mail, event): | |||||||||||||||||||||||
| zope.event.notify(ObjectModifiedEvent(mail, Attributes(ISigningBehavior, "ISigningBehavior.signers"))) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| def dmsoutgoingmail_modified(mail, event): | ||||||||||||||||||||||||
| annot = IAnnotations(mail).get('imio.dms.mail', {}) | ||||||||||||||||||||||||
| copy_dms_files_from = annot.get('copy_dms_files_from') | ||||||||||||||||||||||||
| if copy_dms_files_from: | ||||||||||||||||||||||||
| del annot['copy_dms_files_from'] | ||||||||||||||||||||||||
| original_mail = uuidToObject(copy_dms_files_from, unrestricted=True) | ||||||||||||||||||||||||
| odm_utils = getMultiAdapter((mail, mail.REQUEST), name="odm-utils") | ||||||||||||||||||||||||
| odm_utils.copy_dms_files(original_mail) | ||||||||||||||||||||||||
|
Comment on lines
+574
to
+577
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clear At Line 574 the annotation marker is removed before UUID resolution and copy. If 🛠️ Proposed fix if copy_dms_files_from:
- del annot['copy_dms_files_from']
original_mail = uuidToObject(copy_dms_files_from, unrestricted=True)
+ if original_mail is None:
+ return
odm_utils = getMultiAdapter((mail, mail.REQUEST), name="odm-utils")
odm_utils.copy_dms_files(original_mail)
+ annot.pop('copy_dms_files_from', None)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
Comment on lines
+570
to
+578
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: rg -n '^def dmsoutgoingmail_modified\(' imio/dms/mail/subscribers.pyRepository: IMIO/imio.dms.mail Length of output: 155 🏁 Script executed: # Read context around the first definition at line 475
sed -n '470,490p' imio/dms/mail/subscribers.pyRepository: IMIO/imio.dms.mail Length of output: 844 🏁 Script executed: # Read context around the second definition at line 570
sed -n '565,585p' imio/dms/mail/subscribers.pyRepository: IMIO/imio.dms.mail Length of output: 1085 🏁 Script executed: # Check for `@subscribe` or event handler registrations around both definitions
# Look at lines before first definition
sed -n '468,476p' imio/dms/mail/subscribers.py
echo "---"
# Look at lines before second definition
sed -n '563,571p' imio/dms/mail/subscribers.pyRepository: IMIO/imio.dms.mail Length of output: 860 🏁 Script executed: # Check further back for decorators (up to 10 lines before each definition)
sed -n '465,476p' imio/dms/mail/subscribers.py
echo "---SECOND---"
sed -n '560,571p' imio/dms/mail/subscribers.pyRepository: IMIO/imio.dms.mail Length of output: 1045 🏁 Script executed: # Find ZCML files in the package
find imio/dms/mail -name "*.zcml" -type fRepository: IMIO/imio.dms.mail Length of output: 463 🏁 Script executed: cat imio/dms/mail/subscribers.zcmlRepository: IMIO/imio.dms.mail Length of output: 13755 🏁 Script executed: # Find the end of the first dmsoutgoingmail_modified function (line 475)
# Read from line 475 until we hit the next function definition
sed -n '475,570p' imio/dms/mail/subscribers.py | head -95Repository: IMIO/imio.dms.mail Length of output: 4349
The second definition at line 570 overwrites the first at line 475 in Python. When imported and registered in ZCML, only the second function (which copies DMS files) is available. The first function's critical logic—updating signers based on registry rules and calling Rename the second definition to 🧰 Tools🪛 Ruff (0.15.2)[error] 570-570: Redefinition of unused (F811) [warning] 570-570: Unused function argument: (ARG001) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| def dv_handle_file_creation(obj, event): | ||||||||||||||||||||||||
| """Intermediate function to avoid converting some files in documentviewer""" | ||||||||||||||||||||||||
| if obj.portal_type in DV_AVOIDED_TYPES: | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Permission mismatch: view requires weaker permission than action panel check.
The action panel's
may_duplicate()inactionspanel.pychecks for"Add portal content"permission, but this browser page is registered with"zope2.View". Users could bypass the UI permission check by directly accessing@@om-duplicate. Since the duplicate operation creates new content in the outgoing-mail folder, the permission should be aligned.🔒 Proposed fix to align permissions
<browser:page name="om-duplicate" for="imio.dms.mail.dmsmail.IImioDmsOutgoingMail" - permission="zope2.View" + permission="cmf.AddPortalContent" class=".views.OMDuplicateForm" />📝 Committable suggestion
🤖 Prompt for AI Agents