Commit 672538f2 authored by Romain Courteaud's avatar Romain Courteaud

slapos_crm: support event's attachment

Allow to upload an attachment when creating an ticket's event.

The text_content parameter is limited to text/plain.
More work is required to support text/html content_type.
parent 4d1d68c5
"""Generic script to add event """Generic script to add event
It creates new Event for any context which become follow_up of created Event. It creates new Event for any context which become follow_up of created Event.
""" """
from erp5.component.tool.NotificationTool import buildEmailMessage
from zExceptions import Unauthorized
if REQUEST is not None:
raise Unauthorized
portal = context.getPortalObject() portal = context.getPortalObject()
ticket = context ticket = context
REQUEST = context.REQUEST
# Max ~3Mb
if int(REQUEST.getHeader('Content-Length', 0)) > 3145728:
raise ValueError('Huge attachment is not supported')
# Compatibility with previous usage
if (content_type is not None) and (content_type != 'text/plain'):
raise ValueError('Unsupported text content_type: %s' % content_type)
# Create a mail message to allow attachment in the event
attachment_list = []
if attachment:
# Build dict wrapper for NotificationTool
mime_type = attachment.headers.get('Content-Type', '')
content = attachment.read()
name = getattr(attachment, 'filename', None)
attachment = dict(mime_type=mime_type,
content=content,
name=name)
attachment_list.append(attachment)
if direction == 'outgoing': if direction == 'outgoing':
source_relative_url = source or ticket.getSource() source_relative_url = source or ticket.getSource()
...@@ -21,6 +48,9 @@ elif direction == 'incoming': ...@@ -21,6 +48,9 @@ elif direction == 'incoming':
else: else:
raise NotImplementedError('The specified direction is not handled: %r' % (direction,)) raise NotImplementedError('The specified direction is not handled: %r' % (direction,))
if (notification_message is not None) and (attachment is not None):
raise ValueError('Can not add attachment to a notification message')
event_kw = { event_kw = {
'portal_type': portal_type, 'portal_type': portal_type,
'resource': resource, 'resource': resource,
...@@ -33,8 +63,6 @@ event_kw = { ...@@ -33,8 +63,6 @@ event_kw = {
'start_date': DateTime(), 'start_date': DateTime(),
'follow_up_value': ticket, 'follow_up_value': ticket,
'language': language, 'language': language,
'text_content': text_content,
'content_type': content_type,
} }
# Create event # Create event
module = portal.getDefaultModule(portal_type=portal_type) module = portal.getDefaultModule(portal_type=portal_type)
...@@ -50,7 +78,14 @@ if notification_message: ...@@ -50,7 +78,14 @@ if notification_message:
# Prefer using the notification message title # Prefer using the notification message title
# as it will be correctly translated # as it will be correctly translated
if not event.hasTitle(): if not event.hasTitle():
event.edit(title=title) email = buildEmailMessage(from_url=None,
to_url=None,
msg=text_content,
subject=title,
attachment_list=attachment_list)
event.edit(
data=email.as_string()
)
if not keep_draft: if not keep_draft:
if direction == 'incoming': if direction == 'incoming':
......
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>title, direction, portal_type, resource, text_content, content_type, notification_message=None, substitution_method_parameter_dict=None, keep_draft=False, source=None, destination=None, language=None, comment=None, **kw</string> </value> <value> <string>title, direction, portal_type, resource, text_content, content_type=None, attachment=None, notification_message=None, substitution_method_parameter_dict=None, keep_draft=False, source=None, destination=None, language=None, comment=None, REQUEST=None, **kw</string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
......
...@@ -24,9 +24,12 @@ ...@@ -24,9 +24,12 @@
import transaction import transaction
from erp5.component.test.SlapOSTestCaseMixin import \ from erp5.component.test.SlapOSTestCaseMixin import \
SlapOSTestCaseMixin,SlapOSTestCaseMixinWithAbort, TemporaryAlarmScript, PinnedDateTime SlapOSTestCaseMixin,SlapOSTestCaseMixinWithAbort, TemporaryAlarmScript, PinnedDateTime
from Products.ERP5Type.tests.utils import FileUpload
import os
from DateTime import DateTime from DateTime import DateTime
from App.Common import rfc1123_date from App.Common import rfc1123_date
from zExceptions import Unauthorized
def getFakeSlapState(): def getFakeSlapState():
...@@ -423,3 +426,270 @@ class TestSlapOSFolder_getTicketFeedUrl(TestCRMSkinsMixin): ...@@ -423,3 +426,270 @@ class TestSlapOSFolder_getTicketFeedUrl(TestCRMSkinsMixin):
def test_Folder_getTicketFeedUrl_incident_response_module(self): def test_Folder_getTicketFeedUrl_incident_response_module(self):
self._test(self.portal.incident_response_module) self._test(self.portal.incident_response_module)
class TestTicket_createProjectEvent(TestCRMSkinsMixin):
def createUsualTicket(self):
portal = self.portal
return portal.support_request_module.newContent(
portal_type='Support Request',
title='test support request',
source_value=portal.person_module.newContent(
portal_type='Person',
title='ticket source person'
),
source_section_value=portal.organisation_module.newContent(
portal_type='Organisation',
title='ticket source section organisation'
),
source_project_value=portal.project_module.newContent(
portal_type='Project',
title='ticket source project'
),
destination_value=portal.person_module.newContent(
portal_type='Person',
title='ticket destination person'
),
destination_section_value=portal.organisation_module.newContent(
portal_type='Organisation',
title='ticket destination section organisation'
),
destination_project_value=portal.project_module.newContent(
portal_type='Project',
title='ticket destination project'
),
)
def makeImageFileUpload(self):
import Products.ERP5.tests
file_upload = FileUpload(
os.path.join(os.path.dirname(Products.ERP5.tests.__file__),
'test_data', 'images', 'erp5_logo.png'))
file_upload.headers['Content-Type'] = 'image/png'
return file_upload
def assertSameEventAttachmentList(self, event, information_dict_list=None):
if information_dict_list is None:
information_dict_list = []
event_information_dict_list = []
for information in event.getAttachmentInformationList():
if information['uid'] != information['filename']:
event_information_dict_list.append({
'title': information['filename'],
'content_type': information['content_type'],
'index': information['index']
})
import json
self.assertSameSet(
[json.dumps(x) for x in event_information_dict_list],
[json.dumps(x) for x in information_dict_list]
)
def test_Ticket_createProjectEvent_REQUEST_disallowed(self):
ticket = self.createUsualTicket()
self.assertRaises(
Unauthorized,
ticket.Ticket_createProjectEvent,
'foo_title', 'foo_direction', 'foo portal type',
'foo resource', 'foo text',
REQUEST={})
def test_Ticket_createProjectEvent_incomingEventWithoutAttachment(self):
ticket = self.createUsualTicket()
event = ticket.Ticket_createProjectEvent(
'foo_title', 'incoming', 'Letter',
'foo resource', 'foo text',
)
self.assertEquals(event.getPortalType(), 'Letter')
self.assertEquals(event.getTitle(), 'foo_title')
self.assertEquals(event.getFollowUp(), ticket.getRelativeUrl())
self.assertEquals(event.getResource(), 'foo resource')
self.assertEquals(event.getSource(), ticket.getDestination())
self.assertEquals(event.getSourceSection(), ticket.getDestinationSection())
self.assertEquals(event.getSourceProject(), ticket.getDestinationProject())
self.assertEquals(event.getDestination(), ticket.getSource())
self.assertEquals(event.getDestinationSection(), ticket.getSourceSection())
self.assertEquals(event.getDestinationProject(), ticket.getSourceProject())
self.assertEquals(event.getTextContent(), 'foo text')
self.assertEquals(event.getContentType(), 'text/plain')
self.assertSameEventAttachmentList(event)
self.assertEquals(event.getSimulationState(), 'stopped')
def test_Ticket_createProjectEvent_incomingEventWithoutAttachmentAndNotExistingNotificationMessage(self):
ticket = self.createUsualTicket()
event = ticket.Ticket_createProjectEvent(
'foo_title', 'incoming', 'Letter',
'foo resource', 'foo text',
notification_message='foo notification message'
)
self.assertEquals(event.getPortalType(), 'Letter')
self.assertEquals(event.getTitle(), 'foo_title')
self.assertEquals(event.getFollowUp(), ticket.getRelativeUrl())
self.assertEquals(event.getResource(), 'foo resource')
self.assertEquals(event.getSource(), ticket.getDestination())
self.assertEquals(event.getSourceSection(), ticket.getDestinationSection())
self.assertEquals(event.getSourceProject(), ticket.getDestinationProject())
self.assertEquals(event.getDestination(), ticket.getSource())
self.assertEquals(event.getDestinationSection(), ticket.getSourceSection())
self.assertEquals(event.getDestinationProject(), ticket.getSourceProject())
self.assertEquals(event.getTextContent(), 'foo text')
self.assertEquals(event.getContentType(), 'text/plain')
self.assertSameEventAttachmentList(event)
self.assertEquals(event.getSimulationState(), 'stopped')
def test_Ticket_createProjectEvent_incomingEventWithoutAttachmentAndNotificationMessage(self):
ticket = self.createUsualTicket()
notification_message = self.portal.notification_message_module.newContent(
portal_type='Notification Message',
reference='notification-reference-%s' % self.generateNewId(),
language='en',
version='001',
title='foo notification title',
text_content='<p>foo notification text content</p>',
content_type='text/html'
)
notification_message.validate()
self.tic()
event = ticket.Ticket_createProjectEvent(
'foo_title', 'incoming', 'Letter',
'foo resource', 'foo text',
notification_message=notification_message.getReference(),
language=notification_message.getLanguage()
)
self.assertEquals(event.getPortalType(), 'Letter')
self.assertEquals(event.getTitle(), notification_message.getTitle())
self.assertEquals(event.getFollowUp(), ticket.getRelativeUrl())
self.assertEquals(event.getResource(), 'foo resource')
self.assertEquals(event.getSource(), ticket.getDestination())
self.assertEquals(event.getSourceSection(), ticket.getDestinationSection())
self.assertEquals(event.getSourceProject(), ticket.getDestinationProject())
self.assertEquals(event.getDestination(), ticket.getSource())
self.assertEquals(event.getDestinationSection(), ticket.getSourceSection())
self.assertEquals(event.getDestinationProject(), ticket.getSourceProject())
self.assertEquals(event.getTextContent(), notification_message.getTextContent())
self.assertEquals(event.getContentType(), notification_message.getContentType())
self.assertSameEventAttachmentList(event)
self.assertEquals(event.getSimulationState(), 'stopped')
def test_Ticket_createProjectEvent_incomingEventWithSourceAndDestination(self):
ticket = self.createUsualTicket()
source_value = self.portal.person_module.newContent(
portal_type='Person',
title='custom source person'
)
destination_value = self.portal.person_module.newContent(
portal_type='Person',
title='custom destination person'
)
event = ticket.Ticket_createProjectEvent(
'foo_title', 'incoming', 'Letter',
'foo resource', 'foo text',
source=source_value.getRelativeUrl(),
destination=destination_value.getRelativeUrl()
)
self.assertEquals(event.getPortalType(), 'Letter')
self.assertEquals(event.getTitle(), 'foo_title')
self.assertEquals(event.getFollowUp(), ticket.getRelativeUrl())
self.assertEquals(event.getResource(), 'foo resource')
self.assertEquals(event.getSource(), source_value.getRelativeUrl())
self.assertEquals(event.getSourceSection(), ticket.getDestinationSection())
self.assertEquals(event.getSourceProject(), ticket.getDestinationProject())
self.assertEquals(event.getDestination(), destination_value.getRelativeUrl())
self.assertEquals(event.getDestinationSection(), ticket.getSourceSection())
self.assertEquals(event.getDestinationProject(), ticket.getSourceProject())
self.assertEquals(event.getTextContent(), 'foo text')
self.assertEquals(event.getContentType(), 'text/plain')
self.assertSameEventAttachmentList(event)
self.assertEquals(event.getSimulationState(), 'stopped')
def test_Ticket_createProjectEvent_incomingEventWithAttachment(self):
ticket = self.createUsualTicket()
event = ticket.Ticket_createProjectEvent(
'foo_title', 'incoming', 'Web Message',
'foo resource', 'foo text',
attachment=self.makeImageFileUpload()
)
self.assertEquals(event.getPortalType(), 'Web Message')
self.assertEquals(event.getTitle(), 'foo_title')
self.assertEquals(event.getFollowUp(), ticket.getRelativeUrl())
self.assertEquals(event.getResource(), 'foo resource')
self.assertEquals(event.getSource(), ticket.getDestination())
self.assertEquals(event.getSourceSection(), ticket.getDestinationSection())
self.assertEquals(event.getSourceProject(), ticket.getDestinationProject())
self.assertEquals(event.getDestination(), ticket.getSource())
self.assertEquals(event.getDestinationSection(), ticket.getSourceSection())
self.assertEquals(event.getDestinationProject(), ticket.getSourceProject())
self.assertEquals(event.getTextContent(), 'foo text')
self.assertEquals(event.getContentType(), 'text/plain')
self.assertSameEventAttachmentList(event, [{
"index": 2,
"content_type": "image/png",
"title": "erp5_logo.png"
}])
self.assertEquals(event.getSimulationState(), 'stopped')
def test_Ticket_createProjectEvent_incomingEventWithAttachmentAndNotificationMessage(self):
ticket = self.createUsualTicket()
self.assertRaises(
ValueError,
ticket.Ticket_createProjectEvent,
'foo_title', 'incoming', 'foo portal type',
'foo resource', 'foo text',
notification_message='foo notification reference',
attachment=self.makeImageFileUpload()
)
def test_Ticket_createProjectEvent_outgoingEvent(self):
ticket = self.createUsualTicket()
event = ticket.Ticket_createProjectEvent(
'foo_title', 'outgoing', 'Web Message',
'foo resource', 'foo text',
)
self.assertEquals(event.getPortalType(), 'Web Message')
self.assertEquals(event.getTitle(), 'foo_title')
self.assertEquals(event.getFollowUp(), ticket.getRelativeUrl())
self.assertEquals(event.getResource(), 'foo resource')
self.assertEquals(event.getDestination(), ticket.getDestination())
self.assertEquals(event.getDestinationSection(), ticket.getDestinationSection())
self.assertEquals(event.getDestinationProject(), ticket.getDestinationProject())
self.assertEquals(event.getSource(), ticket.getSource())
self.assertEquals(event.getSourceSection(), ticket.getSourceSection())
self.assertEquals(event.getSourceProject(), ticket.getSourceProject())
self.assertEquals(event.getTextContent(), 'foo text')
self.assertEquals(event.getContentType(), 'text/plain')
self.assertSameEventAttachmentList(event)
self.assertEquals(event.getSimulationState(), 'delivered')
def test_Ticket_createProjectEvent_outgoingEventWithSourceAndDestination(self):
ticket = self.createUsualTicket()
source_value = self.portal.person_module.newContent(
portal_type='Person',
title='custom source person'
)
destination_value = self.portal.person_module.newContent(
portal_type='Person',
title='custom destination person'
)
event = ticket.Ticket_createProjectEvent(
'foo_title', 'outgoing', 'Web Message',
'foo resource', 'foo text',
source=source_value.getRelativeUrl(),
destination=destination_value.getRelativeUrl()
)
self.assertEquals(event.getPortalType(), 'Web Message')
self.assertEquals(event.getTitle(), 'foo_title')
self.assertEquals(event.getFollowUp(), ticket.getRelativeUrl())
self.assertEquals(event.getResource(), 'foo resource')
self.assertEquals(event.getDestination(), destination_value.getRelativeUrl())
self.assertEquals(event.getDestinationSection(), ticket.getDestinationSection())
self.assertEquals(event.getDestinationProject(), ticket.getDestinationProject())
self.assertEquals(event.getSource(), source_value.getRelativeUrl())
self.assertEquals(event.getSourceSection(), ticket.getSourceSection())
self.assertEquals(event.getSourceProject(), ticket.getSourceProject())
self.assertEquals(event.getTextContent(), 'foo text')
self.assertEquals(event.getContentType(), 'text/plain')
self.assertSameEventAttachmentList(event)
self.assertEquals(event.getSimulationState(), 'delivered')
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment