Commit 25e965c3 authored by Jérome Perrin's avatar Jérome Perrin

Support Request App

Assorted fixes and new features for support request app:

Features
 * post are now HTML and use the preferred editor ( CKEditor by default )
 * posts are ingested in Web Message and the app uses same data model as erp5_crm ( so it is able to display support request created with "standard" ERP5 interfaces)
 * date of post uses momentjs relative time (New message by Bob 1 hour ago...)

Bug fixes:
 * post API no longer use proxy roles / immediate reindex
 * RSS was re-implemented to list events. The previous approach of listing support requests had an issue that the date of new posts was still the date of the original support request.
 * attached files to the "submit new support request" dialog where not uploaded
 * using a handlebars template we prevent html injection / XSS 
 * increased test coverage

/reviewed-on !769
parents a42da4de 47099d29
...@@ -112,7 +112,7 @@ ...@@ -112,7 +112,7 @@
<item> <item>
<key> <string>text_content</string> </key> <key> <string>text_content</string> </key>
<value> <string>CACHE MANIFEST\n <value> <string>CACHE MANIFEST\n
# v1.0.2\n # v1.0.3\n
CACHE:\n CACHE:\n
font-awesome/font-awesome-webfont.woff2\n font-awesome/font-awesome-webfont.woff2\n
echarts-all.js\n echarts-all.js\n
......
...@@ -10,10 +10,32 @@ ...@@ -10,10 +10,32 @@
<script src="renderjs.js" type="text/javascript"></script> <script src="renderjs.js" type="text/javascript"></script>
<!-- custom script --> <!-- custom script -->
<script src="handlebars.js" type="text/javascript"></script>
<script src="gadget_erp5_global.js" type="text/javascript"></script> <script src="gadget_erp5_global.js" type="text/javascript"></script>
<script src="gadget_erp5_pt_form_view_discussable.js" type="text/javascript"></script> <script src="gadget_erp5_pt_form_view_discussable.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="gadget_erp5_pt_form_view_discussable.css"> <link rel="stylesheet" type="text/css" href="gadget_erp5_pt_form_view_discussable.css">
<!-- templates -->
<script id="template-document-list" type="text/x-handlebars-template">
{{#if comments }}
{{#each comments }}
<li>By <strong>{{ user }}</strong> -
<time datetime="{{ date }}" title="{{ date_formatted }}">{{ date_relative }}</time>
<br/>
{{{ text }}}
{{#if attachment_link }}
<br/>
<strong>Attachment: </strong>
<a href="{{attachment_link}}">{{ attachment_name }}</a>
{{/if}}
<hr id="post_item">
</li>
{{/each }}
{{else }}
<p><em>No comment yet.</em></p><hr id="post_item">
{{/if }}
</script>
</head> </head>
<body> <body>
<!-- XXX this is a form replacement --> <!-- XXX this is a form replacement -->
...@@ -28,11 +50,15 @@ ...@@ -28,11 +50,15 @@
<p style="background-color:#0E81C2;color:white;margin:1em 0;padding:0.5em">Comments:</p> <p style="background-color:#0E81C2;color:white;margin:1em 0;padding:0.5em">Comments:</p>
<ol id="post_list"></ol> <ol id="post_list"></ol>
<h3 class="ui-content-title ui-body-c ui-icon ui-icon-custom ui-icon-random" id="comment-title" name="comment-title">&nbsp;Post a comment</h3> <h3 class="ui-content-title ui-body-c ui-icon ui-icon-custom ui-icon-random" id="comment-title" name="comment-title">&nbsp;Post a comment</h3>
<textarea id="comment" name="comment" placeholder="Enter your comment here..."></textarea> <div data-gadget-url="gadget_editor.html"
data-gadget-scope="editor"
data-gadget-sandbox="">
</div>
<div id="file_upload_div"> <div id="file_upload_div">
<input value="" name="attachment" id="attachment" type="file" title="Upload"> <input value="" name="attachment" id="attachment" type="file" title="Upload">
</div> </div>
<input data-theme="b" data-inline="true" type="submit" data-i18n="[value]Post Comment" value="Post Comment" data-icon="check" /> <input data-theme="b" data-inline="true" type="submit" data-i18n="[value]Post Comment" value="Post Comment" data-icon="check" disabled class="ui-disabled"/>
</form> </form>
</div> </div>
</body> </body>
......
...@@ -252,8 +252,8 @@ ...@@ -252,8 +252,8 @@
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1506616673.69</float> <float>1539136980.8</float>
<string>UTC</string> <string>GMT+9</string>
</tuple> </tuple>
</state> </state>
</object> </object>
......
/*global window, rJS, RSVP, calculatePageTitle, FormData, URI, jIO*/ /*global window, rJS, RSVP, calculatePageTitle, FormData, URI, jIO, moment, Handlebars */
/*jslint nomen: true, indent: 2, maxerr: 3 */ /*jslint nomen: true, indent: 2, maxerr: 3 */
(function (window, rJS, RSVP, calculatePageTitle) { (function (window, rJS, RSVP, calculatePageTitle, moment, Handlebars) {
"use strict"; "use strict";
var gadget_klass = rJS(window),
comment_list_template = Handlebars.compile(
gadget_klass.__template_element.getElementById("template-document-list").innerHTML
);
rJS(window) gadget_klass
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// Acquired methods // Acquired methods
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
.declareAcquiredMethod("updateHeader", "updateHeader") .declareAcquiredMethod("updateHeader", "updateHeader")
.declareAcquiredMethod("getSetting", "getSetting") .declareAcquiredMethod("getSetting", "getSetting")
.declareAcquiredMethod("getSettingList", "getSettingList")
.declareAcquiredMethod("jio_getAttachment", "jio_getAttachment") .declareAcquiredMethod("jio_getAttachment", "jio_getAttachment")
.declareAcquiredMethod("jio_putAttachment", "jio_putAttachment") .declareAcquiredMethod("jio_putAttachment", "jio_putAttachment")
.declareAcquiredMethod("notifySubmitted", "notifySubmitted") .declareAcquiredMethod("notifySubmitted", "notifySubmitted")
...@@ -68,15 +73,22 @@ ...@@ -68,15 +73,22 @@
}) })
.onStateChange(function () { .onStateChange(function () {
var gadget = this; var gadget = this;
// render the erp5 form // render the erp5 form
return this.getDeclaredGadget("erp5_form") return gadget.getDeclaredGadget("erp5_form")
.push(function (erp5_form) { .push(function (erp5_form) {
return gadget.getDeclaredGadget("editor")
.push(function (editor) {
return [editor, erp5_form];
});
})
.push(function (gadgets) {
var form_options = gadget.state.erp5_form, var form_options = gadget.state.erp5_form,
rendered_form = gadget.state.erp5_document._embedded._view, rendered_form = gadget.state.erp5_document._embedded._view,
preferred_editor = rendered_form.your_preferred_editor.default,
rendered_field, rendered_field,
key; key,
editor = gadgets[0],
erp5_form = gadgets[1];
// Remove all empty fields, and mark all others as non editable // Remove all empty fields, and mark all others as non editable
for (key in rendered_form) { for (key in rendered_form) {
if (rendered_form.hasOwnProperty(key) && (key[0] !== "_")) { if (rendered_form.hasOwnProperty(key) && (key[0] !== "_")) {
...@@ -93,8 +105,25 @@ ...@@ -93,8 +105,25 @@
form_options.erp5_document = gadget.state.erp5_document; form_options.erp5_document = gadget.state.erp5_document;
form_options.form_definition = gadget.state.form_definition; form_options.form_definition = gadget.state.form_definition;
form_options.view = gadget.state.view; form_options.view = gadget.state.view;
return new RSVP.Queue()
return erp5_form.render(form_options); .push(
function () {
return RSVP.all([
erp5_form.render(form_options),
editor.render({
value: "",
key: "comment",
portal_type: "HTML Post",
editable: true,
editor: preferred_editor
})]);
}
).push(function () {
// make our submit button editable
var element = gadget.element.querySelector('input[type="submit"]');
element.removeAttribute('disabled');
element.classList.remove('ui-disabled');
});
}) })
// render the header // render the header
...@@ -124,6 +153,14 @@ ...@@ -124,6 +153,14 @@
page_title: all_result[7] page_title: all_result[7]
}); });
}) })
.push(function () {
// set locale for momentjs
return gadget.getSettingList(["selected_language",
"default_selected_language"]
).push(function (lang_list) {
moment.locale(lang_list[0] || lang_list[1]);
});
})
.push(function () { .push(function () {
return gadget.jio_getAttachment( return gadget.jio_getAttachment(
'post_module', 'post_module',
...@@ -131,107 +168,96 @@ ...@@ -131,107 +168,96 @@
); );
}) })
.push(function (post_list) { .push(function (post_list) {
var i, // XXX abbreviation function getPostWithLinkAndLocalDate(post) {
queue_list = []; post.date_formatted = moment(post.date).format('LLLL');
if (post_list.length) { post.date_relative = moment(post.date).fromNow();
for (i = 0; i < post_list.length; i += 1) { if (post.attachment_link === null) {
if (post_list[i][3] !== null && post_list[i][3].indexOf("image_module") !== -1) { return post;
queue_list.push(gadget.getImageUrl(post_list[i][3]));
} else if (post_list[i][3] !== null && post_list[i][3].indexOf("document_module") !== -1) {
queue_list.push(gadget.getDocumentUrl(post_list[i][3]));
} else {
queue_list.push(null);
}
} }
} if (post.attachment_link.indexOf("image_module") !== -1) {
queue_list.push(post_list); return gadget.getImageUrl(post.attachment_link).push(
return RSVP.all(queue_list); function (attachment_link) {
}) post.attachment_link = attachment_link;
.push(function (result_list) { return post;
var s = '', i, comments = gadget.element.querySelector("#post_list"),
plain_content, post_list = result_list.pop();
if (post_list.length) {
for (i = 0; i < post_list.length; i += 1) {
s += '<li>' +
'By <strong>' + post_list[i][0] + '</strong>' +
' - <time>' + post_list[i][1] + '</time><br/>';
if (post_list[i][3] !== null && result_list[i] !== null) {
post_list[i][3] = result_list[i];
}
if (post_list[i][2]) {
plain_content = post_list[i][2];
if (post_list[i][3]) {
s += plain_content + '<strong>Attachment: </strong>' +
'<a href=\"' +
post_list[i][3] + '\">' + post_list[i][4] +
'</a>';
} else {
s += plain_content;
}
} else {
if (post_list[i][3]) {
s += '<strong>Attachment: </strong>' + '<a href=\"' +
post_list[i][3] + '\">' + post_list[i][4] +
'</a>';
} }
} );
s += '<hr id=post_item>'; // XXX XSS attack!
} }
comments.innerHTML = s; return gadget.getDocumentUrl(post.attachment_link).push(
} else { function (attachment_link) {
comments.innerHTML = "<p><em>No comment yet.</em></p><hr id=post_item>"; post.attachment_link = attachment_link;
return post;
}
);
}
// build links with attachments and localized dates
var queue_list = [], i = 0;
for (i = 0; i < post_list.length; i += 1) {
queue_list.push(getPostWithLinkAndLocalDate(post_list[i]));
} }
return RSVP.all(queue_list);
})
.push(function (comment_list) {
var comments = gadget.element.querySelector("#post_list");
comments.innerHTML = comment_list_template({comments: comment_list});
}); });
}) })
.declareJob('submitPostComment', function () { .declareJob('submitPostComment', function () {
var gadget = this, var gadget = this,
submitButton = null, submitButton = null,
queue = null, queue = null,
editor = gadget.element.querySelector('#comment'); editor = null;
return gadget.getDeclaredGadget("editor")
if (editor.value === '') { .push(function (e) {
return gadget.notifySubmitted({message: "Post content can not be empty!"}); editor = e;
} return e.getContent();
submitButton = gadget.element.querySelector("input[type=submit]");
submitButton.disabled = true;
function enableSubmitButton() {
submitButton.disabled = false;
}
queue = gadget.notifySubmitted({message: "Posting comment"})
.push(function () {
var choose_file_html_element = gadget.element.querySelector('#attachment'),
file_blob = choose_file_html_element.files[0],
url = gadget.hateoas_url + "post_module/PostModule_createHTMLPostForSupportRequest",
data = new FormData();
data.append("follow_up", gadget.options.jio_key);
data.append("predecessor", '');
data.append("data", editor.value);
data.append("file", file_blob);
// XXX: Hack, call jIO.util.ajax directly to pass the file blob
// Because the jio_putAttachment will call readBlobAsText, which
// will broke the binary file. Call the jIO.util.ajax directly
// will not touch the blob
return jIO.util.ajax({
"type": "POST",
"url": url,
"data": data,
"xhrFields": {
withCredentials: true
}
});
})
.push(function () {
return gadget.notifySubmitted({message: "Comment added", status: "success"});
}) })
.push(function () { .push(function (content) {
editor.value = ''; if (content.comment === '') {
return gadget.redirect({command: 'reload'}); return gadget.notifySubmitted({message: "Post content can not be empty!"});
}
submitButton = gadget.element.querySelector("input[type=submit]");
submitButton.disabled = true;
function enableSubmitButton() {
submitButton.disabled = false;
}
queue = gadget.notifySubmitted({message: "Posting comment"})
.push(function () {
var choose_file_html_element = gadget.element.querySelector('#attachment'),
file_blob = choose_file_html_element.files[0],
url = gadget.hateoas_url + "post_module/PostModule_createHTMLPostForSupportRequest",
data = new FormData();
data.append("follow_up", gadget.options.jio_key);
data.append("predecessor", '');
data.append("data", content.comment);
data.append("file", file_blob);
// XXX: Hack, call jIO.util.ajax directly to pass the file blob
// Because the jio_putAttachment will call readBlobAsText, which
// will broke the binary file. Call the jIO.util.ajax directly
// will not touch the blob
return jIO.util.ajax({
"type": "POST",
"url": url,
"data": data,
"xhrFields": {
withCredentials: true
}
});
})
.push(function () {
return gadget.notifySubmitted({message: "Comment added", status: "success"});
})
.push(function () {
editor.changeState({value: ''})
.push(function () {
return gadget.redirect({command: 'reload'});
});
});
queue.push(enableSubmitButton, enableSubmitButton);
return queue;
}); });
queue.then(enableSubmitButton, enableSubmitButton);
return queue;
}) })
.onEvent('submit', function () { .onEvent('submit', function () {
this.submitPostComment(); this.submitPostComment();
}); });
}(window, rJS, RSVP, calculatePageTitle)); }(window, rJS, RSVP, calculatePageTitle, moment, Handlebars));
\ No newline at end of file \ No newline at end of file
...@@ -252,8 +252,8 @@ ...@@ -252,8 +252,8 @@
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1507898069.5</float> <float>1539140577.69</float>
<string>UTC</string> <string>GMT+9</string>
</tuple> </tuple>
</state> </state>
</object> </object>
......
"""Called on a document ingested from a file post on support request app,
after the document is ingested and metadata are discovered.
"""
post = context.getPortalObject().restrictedTraverse(post_relative_url)
# set relation between post and document
post.setSuccessorValueList([context])
post.publish()
context.share()
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>post_relative_url</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Document_afterSupportRequestFilePostIngestion</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
from Products.ERP5Type.ImmediateReindexContextManager import ImmediateReindexContextManager from Products.ERP5Type.Message import translateString
follow_up_value = context.getPortalObject().restrictedTraverse(follow_up) portal = context.getPortalObject()
follow_up_value = portal.restrictedTraverse(follow_up)
assert follow_up_value.getPortalType() == "Support Request" assert follow_up_value.getPortalType() == "Support Request"
follow_up_value.edit() # update modification date if not web_site_relative_url:
web_site_relative_url = context.getWebSiteValue().getRelativeUrl()
web_site = portal.restrictedTraverse(web_site_relative_url)
with ImmediateReindexContextManager() as immediate_reindex_context_manager:
post = context.PostModule_createHTMLPostFromText(
follow_up=follow_up,
data=data,
immediate_reindex_context_manager=immediate_reindex_context_manager,
)
if file not in ("undefined", None): # XXX "undefined" ? should also be fixed in javascript side post = context.PostModule_createHTMLPostFromText(
document_kw = {'batch_mode': True, follow_up=follow_up,
'redirect_to_document': False, data=data,
'file': file} source_reference=source_reference,
document = context.Base_contribute(**document_kw) )
# set relation between post and document
# XXX successor is used as a way to put a relation between the attachment and the post,
# the actual way should be to use a proper container like an Event that will have
# one or several posts and one or several attachments.
post.setSuccessorValueList([document])
# XXX depending on security model this should be changed accordingly
document.publish()
# XXX the UI of support request app should be responsible for generating a unique
# "message id" for each posted message.
if not post.getSourceReference():
post.setSourceReference(post.getId())
ingest_document_tag = 'ingest-%s' % post.getSourceReference()
after_ingest_document_tag = 'after-ingest-%s' % post.getSourceReference()
document = None
if file not in ("undefined", None): # XXX "undefined" ? should also be fixed in javascript side
follow_up_list = []
project = follow_up_value.getSourceProjectValue()
if project is not None:
follow_up_list.append(project.getRelativeUrl())
group = None
section = follow_up_value.getDestinationSectionValue()\
or follow_up_value.getDestinationValue()
if section is not None:
group = section.getGroup()
document_kw = {
'batch_mode': True,
'redirect_to_document': False,
'attach_document_to_context': True,
'follow_up_list': follow_up_list,
'group': group,
'classification': web_site.getLayoutProperty(
'preferred_attached_document_classification') or\
portal.portal_preferences.getPreferredDocumentClassification(),
'file': file,
}
with follow_up_value.defaultActivateParameterDict(
dict(tag=ingest_document_tag), placeless=True):
# XXX this Base_contribute might update in place another document with same reference
# and leave the post with a "dead link" for successor value.
document = follow_up_value.Base_contribute(**document_kw)
# XXX contribution API should allow to call a method on the final ingested document
# after ingestion is complete.
document.activate(
after_tag=ingest_document_tag,
tag=after_ingest_document_tag,
).Document_afterSupportRequestFilePostIngestion(
post_relative_url=post.getRelativeUrl(), )
else:
# when we don't upload a document, we can publish the post now.
post.publish() post.publish()
post.activate(
after_tag=after_ingest_document_tag
# XXX This API is not agreed. Also, we need to consider the possibility
# of ingesting posts through alarm, which is required when we want to ingest
# post without owners (from anoymous users).
).Post_ingestMailMessageForSupportRequest(
web_site_relative_url=web_site_relative_url)
# to be able to display the just posted data in SupportRequest_getCommentPostListAsJson,
# we store it in a session variable.
successor_name = successor_link = None
if document is not None:
successor_link, successor_name = document.getRelativeUrl(), document.getFilename()
portal.portal_sessions[
'%s.latest_comment' % follow_up_value.getRelativeUrl()]['comment_post_list'] = dict(
user=post.Base_getOwnerTitle(),
date=post.getStartDate().ISO8601(),
text=post.asStrippedHTML(),
attachment_link=successor_link,
attachment_name=successor_name,
message_id=post.getSourceReference(),)
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>follow_up, predecessor, data, file</string> </value> <value> <string>follow_up, predecessor, data, file, source_reference=None, web_site_relative_url=None</string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
......
from Products.ERP5Type.Message import translateString
portal = context.getPortalObject()
support_request = context.getFollowUpValue()
web_site_value = portal.restrictedTraverse(web_site_relative_url)
# XXX what to do with PData ?
# As a first step just use a string.
data = str(context.getData())
is_html = context.getPortalType() == 'HTML Post'
if is_html:
# sanitize HTML
data = portal.portal_transforms.convertToData(
'text/x-html-safe',
data,
context=context,
mimetype=context.getContentType())
# lookup a resource and a source.
# It's critical for support request app that we can create movement with source and resouce
# because movements without source & resource does not get indexed in stock table. As this
# app uses Inventory API to list the history of movements, movements needs to be indexed.
resource = web_site_value.getLayoutProperty('preferred_event_resource', None)
if not resource:
resource = portal.portal_preferences.getPreferredEventResource()
assert resource, "No resource configured for event"
source_value = portal.portal_membership.getAuthenticatedMember().getUserValue()
if source_value is None:
# try harder to get a source for non-person users.
source_value = support_request.getSourceSectionValue()
web_message = portal.event_module.newContent(
portal_type='Web Message',
title=context.getTitle() if context.hasTitle() else None,
content_type='text/html' if is_html else 'text/plain',
text_content=data,
follow_up_value=support_request,
aggregate_value_list=[context] + context.getSuccessorValueList(
portal_type=portal.getPortalDocumentTypeList()),
resource=resource,
source_value=source_value,
start_date=context.getCreationDate(),
source_reference=context.getSourceReference())
context.archive(
comment=translateString('Ingested as ${web_message_reference}',
mapping={'web_message_reference': web_message.getReference()}))
web_message.stop()
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>web_site_relative_url</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Post_ingestMailMessageForSupportRequest</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -25,6 +25,8 @@ if description is not None or file is not None: ...@@ -25,6 +25,8 @@ if description is not None or file is not None:
predecessor=None, predecessor=None,
data="" if description is None else description, data="" if description is None else description,
file=file, file=file,
web_site_relative_url=context.getWebSiteValue().getRelativeUrl(),
source_reference=source_reference,
) )
return support_request.Base_redirect('officejs_support_request_view', return support_request.Base_redirect('officejs_support_request_view',
......
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>description, file, resource, title, project, **kwargs</string> </value> <value> <string>description, file, resource, title, project, source_reference=None, **kwargs</string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
......
"""Returns all support requests events for RSS
"""
from Products.PythonScripts.standard import Object
portal = context.getPortalObject()
document_type_list = portal.getPortalDocumentTypeList()
def makeLine(kw):
return Object(**kw)
getSupportRequest_memo = {}
def getSupportRequestInfo(event):
follow_up = event.getFollowUp()
try:
return getSupportRequest_memo[follow_up]
except KeyError:
support_request = portal.restrictedTraverse(follow_up)
getSupportRequest_memo[follow_up] = (
support_request.getTitle(),
support_request.getResourceTranslatedTitle() or '',
support_request.SupportRequest_getSupportRequestLink(),
)
return getSupportRequest_memo[follow_up]
data_list = []
for brain in portal.portal_simulation.getMovementHistoryList(
portal_type=portal.getPortalEventTypeList(),
only_accountable=False,
follow_up_portal_type='Support Request',
omit_input=True,
# XXX we still don't have getCurrentMovementHistoryList
simulation_state=('started', 'stopped', 'delivered'),
limit=limit,
sort_on=(('stock.date', 'desc'),
('uid', 'desc')),):
event = brain.getObject()
(support_request_title,
support_request_category,
support_request_link) = getSupportRequestInfo(event)
data_list.append(
makeLine({
# XXX or {author} commented on {support_request} / {author} opened new Ticket: {support_request} ?
'title': support_request_title,
'category': support_request_category,
'author': brain.node_title,
'link': support_request_link,
'description': event.asStrippedHTML(),
'pubDate': brain.date,
'guid': event.getSourceReference() or event.absolute_url(),
'thumbnail': ( # XXX this is not really a thumbnail, but it's what RSS style uses for <enclosure/>
# Also, with this `thumbnail` it will look good for image, and most of the time
# users attach a screenshot of their problem.
event.getDefaultAggregate(portal_type=document_type_list)
and event.getDefaultAggregateValue(portal_type=document_type_list).File_getDownloadUrl()
or None)
}
)
)
return data_list
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>limit=50, **kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SupportRequestModule_getEventList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -88,24 +88,13 @@ ...@@ -88,24 +88,13 @@
<item> <item>
<key> <string>hidden</string> </key> <key> <string>hidden</string> </key>
<value> <value>
<list> <list/>
<string>listbox_delivery_stop_date</string>
<string>listbox_destination_decision_language</string>
<string>listbox_causality_translated_portal_type</string>
<string>listbox_delivery_start_date</string>
<string>listbox_post_start_date_hidden</string>
<string>listbox_post_user</string>
<string>listbox_post_start_date</string>
</list>
</value> </value>
</item> </item>
<item> <item>
<key> <string>left</string> </key> <key> <string>left</string> </key>
<value> <value>
<list> <list/>
<string>listbox_post_content</string>
<string>listbox_support_link</string>
</list>
</value> </value>
</item> </item>
<item> <item>
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
<list> <list>
<string>all_columns</string> <string>all_columns</string>
<string>columns</string> <string>columns</string>
<string>count_method</string>
<string>default_params</string> <string>default_params</string>
<string>domain_root_list</string> <string>domain_root_list</string>
<string>domain_tree</string> <string>domain_tree</string>
...@@ -151,76 +152,7 @@ ...@@ -151,76 +152,7 @@
<item> <item>
<key> <string>all_columns</string> </key> <key> <string>all_columns</string> </key>
<value> <value>
<list> <list/>
<tuple>
<string>destination_section_title</string>
<string>Client</string>
</tuple>
<tuple>
<string>source_section_title</string>
<string>Supplier</string>
</tuple>
<tuple>
<string>source_decision_title</string>
<string>Supervisor</string>
</tuple>
<tuple>
<string>destination_title</string>
<string>Location</string>
</tuple>
<tuple>
<string>post_user</string>
<string>author</string>
</tuple>
<tuple>
<string>resource_title</string>
<string>Type</string>
</tuple>
<tuple>
<string>source_trade_title</string>
<string>Operator</string>
</tuple>
<tuple>
<string>quantity</string>
<string>Quantity</string>
</tuple>
<tuple>
<string>quantity_unit_title</string>
<string>Quantity Unit</string>
</tuple>
<tuple>
<string>post_start_date_hidden</string>
<string>Comment Date</string>
</tuple>
<tuple>
<string>Ticket_getCausalitySourcePersonDefaultEmailText</string>
<string>Requester Email</string>
</tuple>
<tuple>
<string>Ticket_getCausalityAsText</string>
<string>Message Text</string>
</tuple>
<tuple>
<string>destination_decision_reference</string>
<string>Requester Reference</string>
</tuple>
<tuple>
<string>owner_title</string>
<string>Owner</string>
</tuple>
<tuple>
<string>creation_date</string>
<string>Creation Date</string>
</tuple>
<tuple>
<string>modification_date</string>
<string>Modification Date</string>
</tuple>
<tuple>
<string>delivery.stop_date</string>
<string>End Date</string>
</tuple>
</list>
</value> </value>
</item> </item>
<item> <item>
...@@ -232,28 +164,40 @@ ...@@ -232,28 +164,40 @@
<string>Title</string> <string>Title</string>
</tuple> </tuple>
<tuple> <tuple>
<string>resource_title</string> <string>category</string>
<string>category</string> <string>category</string>
</tuple> </tuple>
<tuple> <tuple>
<string>destination_decision_title</string> <string>author</string>
<string>author</string> <string>author</string>
</tuple> </tuple>
<tuple> <tuple>
<string>delivery.start_date</string> <string>pubDate</string>
<string>pubdate</string> <string>pubDate</string>
</tuple> </tuple>
<tuple> <tuple>
<string>post_content</string> <string>description</string>
<string>description</string> <string>description</string>
</tuple> </tuple>
<tuple> <tuple>
<string>support_link</string>
<string>link</string> <string>link</string>
<string>link</string>
</tuple>
<tuple>
<string>guid</string>
<string>guid</string>
</tuple>
<tuple>
<string>thumbnail</string>
<string>thumbnail</string>
</tuple> </tuple>
</list> </list>
</value> </value>
</item> </item>
<item>
<key> <string>count_method</string> </key>
<value> <string></string> </value>
</item>
<item> <item>
<key> <string>default_params</string> </key> <key> <string>default_params</string> </key>
<value> <value>
...@@ -263,16 +207,7 @@ ...@@ -263,16 +207,7 @@
<item> <item>
<key> <string>domain_root_list</string> </key> <key> <string>domain_root_list</string> </key>
<value> <value>
<list> <list/>
<tuple>
<string>ticket_use_domain</string>
<string>Type</string>
</tuple>
<tuple>
<string>causality_event_portal_type_domain</string>
<string>Channel</string>
</tuple>
</list>
</value> </value>
</item> </item>
<item> <item>
...@@ -286,16 +221,7 @@ ...@@ -286,16 +221,7 @@
<item> <item>
<key> <string>editable_columns</string> </key> <key> <string>editable_columns</string> </key>
<value> <value>
<list> <list/>
<tuple>
<string>post_start_date</string>
<string>Comment Date</string>
</tuple>
<tuple>
<string>post_user</string>
<string>Comment Author</string>
</tuple>
</list>
</value> </value>
</item> </item>
<item> <item>
...@@ -327,87 +253,13 @@ ...@@ -327,87 +253,13 @@
<item> <item>
<key> <string>portal_types</string> </key> <key> <string>portal_types</string> </key>
<value> <value>
<list> <list/>
<tuple>
<string>Support Request</string>
<string>Support Request</string>
</tuple>
</list>
</value> </value>
</item> </item>
<item> <item>
<key> <string>search_columns</string> </key> <key> <string>search_columns</string> </key>
<value> <value>
<list> <list/>
<tuple>
<string>title</string>
<string>Title</string>
</tuple>
<tuple>
<string>destination_section_title</string>
<string>Client</string>
</tuple>
<tuple>
<string>destination_decision_title</string>
<string>Requester</string>
</tuple>
<tuple>
<string>destination_decision_language</string>
<string>Requester Language</string>
</tuple>
<tuple>
<string>source_section_title</string>
<string>Supplier</string>
</tuple>
<tuple>
<string>source_decision_title</string>
<string>Supervisor</string>
</tuple>
<tuple>
<string>source_title</string>
<string>Operations Manager</string>
</tuple>
<tuple>
<string>destination_title</string>
<string>Location</string>
</tuple>
<tuple>
<string>start_date</string>
<string>Begin Date</string>
</tuple>
<tuple>
<string>resource_title</string>
<string>Type</string>
</tuple>
<tuple>
<string>post_user</string>
<string>author</string>
</tuple>
<tuple>
<string>causality_translated_portal_type</string>
<string>Channel</string>
</tuple>
<tuple>
<string>source_trade_title</string>
<string>Operator</string>
</tuple>
<tuple>
<string>quantity</string>
<string>Quantity</string>
</tuple>
<tuple>
<string>quantity_unit_title</string>
<string>Quantity Unit</string>
</tuple>
<tuple>
<string>creation_date</string>
<string>Creation Date</string>
</tuple>
<tuple>
<string>modification_date</string>
<string>Modification Date</string>
</tuple>
</list>
</value> </value>
</item> </item>
<item> <item>
...@@ -457,7 +309,7 @@ ...@@ -457,7 +309,7 @@
<dictionary> <dictionary>
<item> <item>
<key> <string>method_name</string> </key> <key> <string>method_name</string> </key>
<value> <string>Folder_searchFolderSortByReferenceDescending</string> </value> <value> <string>SupportRequestModule_getEventList</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>default</string>
<string>editable</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>listbox_causality_translated_portal_type</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python:getattr(cell.getCausalityValue(), \'getTranslatedPortalType\', lambda:None)()</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>editable</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>listbox_delivery_start_date</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_date</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>listbox_delivery_stop_date</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_date_time_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>default</string>
<string>editable</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>listbox_destination_decision_language</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python:cell.hasDestinationDecision() and cell.getDestinationDecisionValue().getLanguage()</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>default</string>
<string>editable</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>listbox_post_content</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: cell.SupportRequest_getLastPostContent()</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>default</string>
<string>editable</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>listbox_post_start_date</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: cell.SupportRequest_getLastPostDate()</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>default</string>
<string>editable</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>listbox_post_user</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: cell.SupportRequest_getLastPostAuthor()</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>default</string>
<string>editable</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>listbox_support_link</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: cell.SupportRequest_getSupportRequestLink()</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
</item> </item>
<item> <item>
<key> <string>enctype</string> </key> <key> <string>enctype</string> </key>
<value> <string></string> </value> <value> <string>multipart/form-data</string> </value>
</item> </item>
<item> <item>
<key> <string>group_list</string> </key> <key> <string>group_list</string> </key>
...@@ -75,7 +75,7 @@ ...@@ -75,7 +75,7 @@
<key> <string>bottom</string> </key> <key> <string>bottom</string> </key>
<value> <value>
<list> <list>
<string>my_description</string> <string>your_description</string>
<string>your_file</string> <string>your_file</string>
</list> </list>
</value> </value>
......
...@@ -10,15 +10,15 @@ ...@@ -10,15 +10,15 @@
<key> <string>delegated_list</string> </key> <key> <string>delegated_list</string> </key>
<value> <value>
<list> <list>
<string>default</string>
<string>editable</string> <string>editable</string>
<string>input_order</string> <string>renderjs_extra</string>
<string>title</string>
</list> </list>
</value> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>listbox_post_start_date_hidden</string> </value> <value> <string>your_description</string> </value>
</item> </item>
<item> <item>
<key> <string>message_values</string> </key> <key> <string>message_values</string> </key>
...@@ -54,12 +54,6 @@ ...@@ -54,12 +54,6 @@
<key> <string>tales</string> </key> <key> <string>tales</string> </key>
<value> <value>
<dictionary> <dictionary>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item> <item>
<key> <string>editable</string> </key> <key> <string>editable</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
...@@ -73,13 +67,19 @@ ...@@ -73,13 +67,19 @@
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>input_order</string> </key> <key> <string>renderjs_extra</string> </key>
<value> <string></string> </value> <value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item> </item>
<item> <item>
<key> <string>target</string> </key> <key> <string>target</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary> </dictionary>
</value> </value>
</item> </item>
...@@ -87,32 +87,32 @@ ...@@ -87,32 +87,32 @@
<key> <string>values</string> </key> <key> <string>values</string> </key>
<value> <value>
<dictionary> <dictionary>
<item>
<key> <string>default</string> </key>
<value>
<none/>
</value>
</item>
<item> <item>
<key> <string>editable</string> </key> <key> <string>editable</string> </key>
<value> <int>0</int> </value> <value> <int>1</int> </value>
</item> </item>
<item> <item>
<key> <string>field_id</string> </key> <key> <string>field_id</string> </key>
<value> <string>my_date</string> </value> <value> <string>my_text_content</string> </value>
</item> </item>
<item> <item>
<key> <string>form_id</string> </key> <key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value> <value> <string>Base_viewCRMFieldLibrary</string> </value>
</item> </item>
<item> <item>
<key> <string>input_order</string> </key> <key> <string>renderjs_extra</string> </key>
<value> <string>ymd</string> </value> <value>
<list/>
</value>
</item> </item>
<item> <item>
<key> <string>target</string> </key> <key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value> <value> <string>Click to edit the target</string> </value>
</item> </item>
<item>
<key> <string>title</string> </key>
<value> <string>Description</string> </value>
</item>
</dictionary> </dictionary>
</value> </value>
</item> </item>
...@@ -127,7 +127,7 @@ ...@@ -127,7 +127,7 @@
<dictionary> <dictionary>
<item> <item>
<key> <string>_text</string> </key> <key> <string>_text</string> </key>
<value> <string>python: cell.SupportRequest_getLastPostDate(is_pure_date=True)</string> </value> <value> <string>python: [(\'editor\', context.Base_getEditorFieldPreferredTextEditor()), (\'portal_type\', \'HTML Post\'), (\'maximize\', False)]</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
from json import dumps from json import dumps
portal = context.getPortalObject() portal = context.getPortalObject()
document_type_list = portal.getPortalDocumentTypeList()
preferred_date_order = portal.portal_preferences.getPreferredDateOrder() or "ymd" event_list = portal.portal_simulation.getMovementHistoryList(
preferred_date_order = "/".join(preferred_date_order) portal_type=portal.getPortalEventTypeList(),
def formatDate(date): strict_follow_up_uid=context.getUid(),
# XXX modification date & creation date are still in server timezone. simulation_state=('started', 'stopped', 'delivered', ),
# See merge request !17 only_accountable=False,
# omit_input=True,
# if default_time_zone: sort_on=(('date', 'asc'), ('uid', 'asc',),)
# date = date.toZone(default_time_zone)
return date.strftime("%s %%H:%%M" %(
preferred_date_order.
replace("y", "%Y").
replace("m", "%m").
replace("d", "%d"),
))
post_list = portal.portal_catalog(
portal_type="HTML Post",
strict_follow_up_uid=context.getUid(),
sort_on=(('modification_date', 'ascending'),),
validation_state="published",
) )
comment_list = [] comment_list = []
for post in post_list: for event in event_list:
owner = post.Base_getOwnerTitle() event = event.getObject()
time_stamp = formatDate(post.getStartDate())
content = post.asStrippedHTML() attachment_link = attachment_name = None
successor_list = post.getSuccessorValueList() attachment = event.getDefaultAggregateValue(portal_type=document_type_list)
successor_name = successor_link = None if attachment is not None:
if successor_list: attachment_link, attachment_name = attachment.getRelativeUrl(), attachment.getFilename()
successor_link, successor_name = successor_list[0].getRelativeUrl(), successor_list[0].getFilename()
comment_list.append((owner, time_stamp, content, successor_link, successor_name)) comment_list.append((dict(
user=event.getSourceTitle(),
date=event.getStartDate().ISO8601(),
text=event.asStrippedHTML(),
attachment_link=attachment_link,
attachment_name=attachment_name,
message_id=event.getSourceReference(),
)))
just_posted_comment = portal.portal_sessions[
'%s.latest_comment' % context.getRelativeUrl()].pop(
'comment_post_list', None)
if just_posted_comment is not None:
# make sure not to display twice if it was already ingested in the meantime.
if just_posted_comment['message_id'] not in [comment['message_id'] for comment in comment_list]:
comment_list.append(just_posted_comment)
return dumps(comment_list) return dumps(comment_list)
portal = context.getPortalObject()
# get the all HTML Posts which related to this Support Request
post_list = portal.portal_catalog(portal_type="HTML Post", strict_follow_up_uid=context.getUid(), sort_on=(('modification_date', 'descending'),), limit=1, validation_state="published") # with id keyword, this function will return a sequence data type which contains one element.
if len(post_list):
return post_list[0].asStrippedHTML()
else:
return None
...@@ -100,6 +100,7 @@ ...@@ -100,6 +100,7 @@
<string>my_source_decision_title</string> <string>my_source_decision_title</string>
<string>my_source_project_title</string> <string>my_source_project_title</string>
<string>my_destination_title</string> <string>my_destination_title</string>
<string>your_preferred_editor</string>
</list> </list>
</value> </value>
</item> </item>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="StringField" module="Products.Formulator.StandardFields"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>your_preferred_editor</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
<item>
<key> <string>required_not_found</string> </key>
<value> <string>Input is required but no input given.</string> </value>
</item>
<item>
<key> <string>too_long</string> </key>
<value> <string>Too much input was given.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_maxwidth</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>input_type</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>truncate</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_maxwidth</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>input_type</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>truncate</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string>hidden</string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_maxwidth</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <int>20</int> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>input_type</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Preferred Editor</string> </value>
</item>
<item>
<key> <string>truncate</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <int>0</int> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>context/Base_getEditorFieldPreferredTextEditor</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<thead> <thead>
<tr><td rowspan="1" colspan="3">Support Request Zuite</td></tr> <tr><td rowspan="1" colspan="3">Support Request Zuite</td></tr>
</thead><tbody> </thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" /> <tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/init" />
<tr> <tr>
<td>open</td> <td>open</td>
<td>${base_url}/web_site_module/erp5_officejs_support_request_ui/</td> <td>${base_url}/web_site_module/erp5_officejs_support_request_ui/</td>
...@@ -49,16 +49,9 @@ ...@@ -49,16 +49,9 @@
<td>field_your_resource</td> <td>field_your_resource</td>
<td>FeatureRequire</td> <td>FeatureRequire</td>
</tr> </tr>
<tr> <tal:block tal:define="text_content string:Post test">
<td>waitForElementPresent</td> <tal:block metal:use-macro="container/Zuite_CommonTemplateForRenderjsUi/macros/type_ckeditor_text_content"/>
<td>//textarea[@id='field_my_description']</td> </tal:block>
<td></td>
</tr>
<tr>
<td>type</td>
<td>//textarea[@id='field_my_description']</td>
<td>Post test</td>
</tr>
<tr> <tr>
<td>click</td> <td>click</td>
<td>//input[@data-i18n='[value]Proceed']</td> <td>//input[@data-i18n='[value]Proceed']</td>
...@@ -74,6 +67,11 @@ ...@@ -74,6 +67,11 @@
<td>Comments:</td> <td>Comments:</td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>waitForText</td>
<td>//ol[@id="post_list"]//li/p</td>
<td>Post test</td>
</tr>
<tr> <tr>
<td>waitForTextPresent</td> <td>waitForTextPresent</td>
<td>Reference</td> <td>Reference</td>
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<thead> <thead>
<tr><td rowspan="1" colspan="3">Support Request Zuite</td></tr> <tr><td rowspan="1" colspan="3">Support Request Zuite</td></tr>
</thead><tbody> </thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" /> <tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/init" />
<tr> <tr>
<td>open</td> <td>open</td>
<td>${base_url}/web_site_module/erp5_officejs_support_request_ui/</td> <td>${base_url}/web_site_module/erp5_officejs_support_request_ui/</td>
...@@ -49,16 +49,9 @@ ...@@ -49,16 +49,9 @@
<td>field_your_resource</td> <td>field_your_resource</td>
<td>FeatureRequire</td> <td>FeatureRequire</td>
</tr> </tr>
<tr> <tal:block tal:define="text_content string:Post test 1">
<td>waitForElementPresent</td> <tal:block metal:use-macro="container/Zuite_CommonTemplateForRenderjsUi/macros/type_ckeditor_text_content"/>
<td>//textarea[@id='field_my_description']</td> </tal:block>
<td></td>
</tr>
<tr>
<td>type</td>
<td>//textarea[@id='field_my_description']</td>
<td>Post test 1</td>
</tr>
<tr> <tr>
<td>waitForElementPresent</td> <td>waitForElementPresent</td>
<td>//input[@data-i18n='[value]Proceed']</td> <td>//input[@data-i18n='[value]Proceed']</td>
...@@ -75,25 +68,136 @@ ...@@ -75,25 +68,136 @@
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
<td>waitForElementPresent</td> <td>waitForText</td>
<td>//textarea[@id='comment']</td> <td>//ol[@id="post_list"]//li[1]/p</td>
<td>Post test 1</td>
</tr>
<tr>
<td>waitForText</td>
<td>//ol[@id="post_list"]//li[1]/p</td>
<td>Post test 1</td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//ol[@id="post_list"]//li[1]/strong</td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
<td>type</td> <td>assertText</td>
<td>//textarea[@id='comment']</td> <td>//ol[@id="post_list"]//li[1]/time</td>
<td>Post test 2</td> <td>a few seconds ago</td>
</tr>
<!-- The "just posted" message is available because it is retrieved from memcached,
eventhough it's not ingested yet. But this works only for one message, so to first message
posted when opening the SR and the second one posted as a comment, we need to flush
activities in between.
Not really good on one hand, because if activities are congested, there will be a problem
here, but on the other hand it allows us to see that once ingested messages are also properly
displayed
-->
<tr>
<td>store</td>
<td>javascript{selenium.browserbot.getCurrentWindow().location.href}</td>
<td>current_location</td>
</tr> </tr>
<tr>
<td>open</td>
<td tal:content="string:${here/portal_url}/Zuite_waitForActivities"/>
<td/>
</tr>
<tr>
<td>waitForTextPresent</td>
<td>Done.</td>
<td/>
</tr>
<tr>
<td>open</td>
<td>${current_location}</td>
<td></td>
</tr>
<tal:block tal:define="text_content string:Post test 2">
<tal:block metal:use-macro="container/Zuite_CommonTemplateForRenderjsUi/macros/type_ckeditor_text_content"/>
</tal:block>
<tr> <tr>
<td>click</td> <td>click</td>
<td>//input[@data-i18n='[value]Post Comment']</td> <td>//input[@data-i18n='[value]Post Comment']</td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>waitForText</td>
<td>//ol[@id="post_list"]//li[2]/p</td>
<td>Post test 2</td>
</tr>
<tr>
<td>assertText</td>
<td>//ol[@id="post_list"]//li[1]/p</td>
<td>Post test 1</td>
</tr>
<tr>
<td>assertText</td>
<td>//ol[@id="post_list"]//li[1]/strong</td>
<td>A1 Corporation</td>
</tr>
<tr>
<td>assertText</td>
<td>//ol[@id="post_list"]//li[1]/time</td>
<td>a few seconds ago</td>
</tr>
<!-- flush activities and post one more message, to exercice ingesting post
posted from support request comment, which uses a different code path than
post ingested when submitting a new support request.
-->
<tr>
<td>store</td>
<td>javascript{selenium.browserbot.getCurrentWindow().location.href}</td>
<td>current_location</td>
</tr>
<tr>
<td>open</td>
<td tal:content="string:${here/portal_url}/Zuite_waitForActivities"/>
<td/>
</tr>
<tr> <tr>
<td>waitForTextPresent</td> <td>waitForTextPresent</td>
<td>By</td> <td>Done.</td>
<td/>
</tr>
<tr>
<td>open</td>
<td>${current_location}</td>
<td></td>
</tr>
<tal:block tal:define="text_content string:Post test 3">
<tal:block metal:use-macro="container/Zuite_CommonTemplateForRenderjsUi/macros/type_ckeditor_text_content"/>
</tal:block>
<tr>
<td>click</td>
<td>//input[@data-i18n='[value]Post Comment']</td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>waitForText</td>
<td>//ol[@id="post_list"]//li[3]/p</td>
<td>Post test 3</td>
</tr>
<tr>
<td>assertText</td>
<td>//ol[@id="post_list"]//li[1]/p</td>
<td>Post test 1</td>
</tr>
<tr>
<td>assertText</td>
<td>//ol[@id="post_list"]//li[2]/p</td>
<td>Post test 2</td>
</tr>
</tbody></table> </tbody></table>
</body> </body>
</html> </html>
\ No newline at end of file
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<thead> <thead>
<tr><td rowspan="1" colspan="3">Support Request Zuite</td></tr> <tr><td rowspan="1" colspan="3">Support Request Zuite</td></tr>
</thead><tbody> </thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" /> <tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/init" />
<tr> <tr>
<td>open</td> <td>open</td>
<td>${base_url}/web_site_module/erp5_officejs_support_request_ui/</td> <td>${base_url}/web_site_module/erp5_officejs_support_request_ui/</td>
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<thead> <thead>
<tr><td rowspan="1" colspan="3">Support Request Zuite</td></tr> <tr><td rowspan="1" colspan="3">Support Request Zuite</td></tr>
</thead><tbody> </thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" /> <tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/init" />
<tr> <tr>
<td>open</td> <td>open</td>
<td>${base_url}/web_site_module/erp5_officejs_support_request_ui/</td> <td>${base_url}/web_site_module/erp5_officejs_support_request_ui/</td>
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<thead> <thead>
<tr><td colspan="3" tal:content="template/title"></td></tr> <tr><td colspan="3" tal:content="template/title"></td></tr>
</thead><tbody> </thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" /> <tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/init" />
<tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/cleanup_module" /> <tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/cleanup_module" />
<tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/create_data" /> <tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/create_data" />
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<thead> <thead>
<tr><td colspan="3" tal:content="template/title"></td></tr> <tr><td colspan="3" tal:content="template/title"></td></tr>
</thead><tbody> </thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" /> <tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/init" />
<tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/cleanup_module" /> <tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/cleanup_module" />
<tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/create_data" /> <tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/create_data" />
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<thead> <thead>
<tr><td rowspan="1" colspan="3">Support Request Zuite</td></tr> <tr><td rowspan="1" colspan="3">Support Request Zuite</td></tr>
</thead><tbody> </thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" /> <tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/init" />
<tr> <tr>
<td>open</td> <td>open</td>
<td>${base_url}/web_site_module/erp5_officejs_support_request_ui/</td> <td>${base_url}/web_site_module/erp5_officejs_support_request_ui/</td>
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<thead> <thead>
<tr><td rowspan="1" colspan="3">Support Request Zuite</td></tr> <tr><td rowspan="1" colspan="3">Support Request Zuite</td></tr>
</thead><tbody> </thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" /> <tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/init" />
<tr> <tr>
<td>open</td> <td>open</td>
<td>${base_url}/web_site_module/erp5_officejs_support_request_ui/</td> <td>${base_url}/web_site_module/erp5_officejs_support_request_ui/</td>
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<thead> <thead>
<tr><td rowspan="1" colspan="3">Support Request Zuite</td></tr> <tr><td rowspan="1" colspan="3">Support Request Zuite</td></tr>
</thead><tbody> </thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" /> <tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/init" />
<tr> <tr>
<td>open</td> <td>open</td>
<td>${base_url}/web_site_module/erp5_officejs_support_request_ui/</td> <td>${base_url}/web_site_module/erp5_officejs_support_request_ui/</td>
...@@ -24,16 +24,9 @@ ...@@ -24,16 +24,9 @@
<td>//input[@data-i18n='[value]Submit New Support Request']</td> <td>//input[@data-i18n='[value]Submit New Support Request']</td>
<td></td> <td></td>
</tr> </tr>
<tr> <tal:block tal:define="text_content string:Post test">
<td>waitForElementPresent</td> <tal:block metal:use-macro="container/Zuite_CommonTemplateForRenderjsUi/macros/type_ckeditor_text_content"/>
<td>//textarea[@id='field_my_description']</td> </tal:block>
<td></td>
</tr>
<tr>
<td>type</td>
<td>//textarea[@id='field_my_description']</td>
<td>Post test</td>
</tr>
<tr> <tr>
<td>type</td> <td>type</td>
<td>//input[@name='field_your_title']</td> <td>//input[@name='field_your_title']</td>
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<thead> <thead>
<tr><td rowspan="1" colspan="3">Worklists on support request front page</td></tr> <tr><td rowspan="1" colspan="3">Worklists on support request front page</td></tr>
</thead><tbody> </thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" /> <tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/init" />
<tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/cleanup_module" /> <tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/cleanup_module" />
<tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/create_data" /> <tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/create_data" />
......
...@@ -52,14 +52,6 @@ ...@@ -52,14 +52,6 @@
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>ERP5Site_addManagerAclUserIfNotExisting</string> </value> <value> <string>ERP5Site_addManagerAclUserIfNotExisting</string> </value>
......
...@@ -20,6 +20,12 @@ for brain in portal.portal_catalog( ...@@ -20,6 +20,12 @@ for brain in portal.portal_catalog(
to_delete_list.append(support_request.getId()) to_delete_list.append(support_request.getId())
portal.support_request_module.manage_delObjects(to_delete_list) portal.support_request_module.manage_delObjects(to_delete_list)
event_to_delete_id_list = []
for event in portal.event_module.contentValues():
if event.getFollowUp(portal_type='Support Request'):
event_to_delete_id_list.append(event.getId())
portal.event_module.manage_delObjects(event_to_delete_id_list)
# Clear worklist cache # Clear worklist cache
portal.portal_caches.clearAllCache() portal.portal_caches.clearAllCache()
......
portal = context.getPortalObject()
# This should have been created during setup by Zuite_setPreference, but
# we need to support cases where developer run test in his development
# instance without the setup steps of ERP5TypeFunctionalTestCase
pref = getattr(portal.portal_preferences, "erp5_ui_test_preference", None)
if pref is None:
pref = portal.portal_preferences.newContent(
id="erp5_ui_test_preference",
portal_type="Preference",
priority=1)
if pref.getPreferenceState() == 'disabled':
pref.enable()
# use fck editor, we test with this
pref.setPreferredTextEditor('fck_editor')
# set a preferred event resource, so that the web message we create
# gets indexed properly in stock table.
# XXX This ressource does not make much sense though, using something like
# "support request message post" would be closer to the real resource of
# these events.
preferred_event_resource = 'service_module/erp5_officejs_support_request_ui_test_service_003'
if portal.web_site_module.erp5_officejs_support_request_ui.getLayoutProperty(
'preferred_event_resource', None) != 'preferred_event_resource':
portal.web_site_module.erp5_officejs_support_request_ui.edit(
preferred_event_resource=preferred_event_resource
)
return "Done."
...@@ -54,7 +54,7 @@ ...@@ -54,7 +54,7 @@
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>SupportRequest_getLastPostContent</string> </value> <value> <string>ERP5Site_setupSupportRequestPreference</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
<tal:block xmlns:tal="http://xml.zope.org/namespaces/tal" <tal:block xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal"> xmlns:metal="http://xml.zope.org/namespaces/metal">
<tal:block metal:define-macro="init">
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" />
<tr><td>openAndWait</td>
<td>${base_url}/ERP5Site_setupSupportRequestPreference</td><td></td></tr>
<tr><td>assertTextPresent</td>
<td>Done.</td><td></td></tr>
</tal:block>
<tal:block metal:define-macro="cleanup_module"> <tal:block metal:define-macro="cleanup_module">
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/wait_for_activities" /> <tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/wait_for_activities" />
<tr><td>openAndWait</td> <tr><td>openAndWait</td>
......
...@@ -25,23 +25,9 @@ ...@@ -25,23 +25,9 @@
# #
############################################################################## ##############################################################################
import unittest
from Products.ERP5Type.tests.ERP5TypeFunctionalTestCase import ERP5TypeFunctionalTestCase from Products.ERP5Type.tests.ERP5TypeFunctionalTestCase import ERP5TypeFunctionalTestCase
class TestSupportRequestUI(ERP5TypeFunctionalTestCase): class TestSupportRequestUI(ERP5TypeFunctionalTestCase):
foreground = 0
run_only = "officejs_support_request_ui_zuite" run_only = "officejs_support_request_ui_zuite"
def getBusinessTemplateList(self):
return (
'erp5_web_renderjs_ui',
'erp5_web_renderjs_ui_test',
'erp5_ui_test_core',
'erp5_officejs_support_request_ui'
)
def test_suite(): del ERP5TypeFunctionalTestCase
suite = unittest.TestSuite() \ No newline at end of file
suite.addTest(unittest.makeSuite(TestSupportRequestUI))
return suite
\ No newline at end of file
##############################################################################
#
# Copyright (c) 2018- Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################
import json
from StringIO import StringIO
import urlparse
import httplib
import feedparser
from DateTime import DateTime
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
class FileUpload(StringIO):
filename = 'attached_file.txt'
def ignoreKeys(list_of_dict, *ignored):
"""remove some keys from each dict dict to compare except some ignored keys.
"""
new_list = []
for d in list_of_dict:
d = d.copy()
for k in ignored:
d.pop(k, None)
new_list.append(d)
return new_list
class SupportRequestTestCase(ERP5TypeTestCase, object):
def afterSetUp(self):
ERP5TypeTestCase.afterSetUp(self)
self.assertEqual(self.portal.ERP5Site_setupSupportRequestPreference(), 'Done.')
self.tic()
self.assertEqual(self.portal.ERP5Site_createSupportRequestUITestDataSet(), 'Done.')
self.tic()
self.createUserAndLogin()
def beforeTearDown(self):
self.abort()
self.tic()
self.portal.person_module.manage_delObjects(
[self.user.getId()])
self.assertEqual(self.portal.ERP5Site_cleanupSupportRequestUITestDataSet(), 'Done.')
self.tic()
def createUserAndLogin(self):
self.user = self.portal.person_module.newContent(
first_name=self.id()
)
self.user.newContent(
portal_type='Assignment'
).open()
self.user_password = self.newPassword()
self.user.newContent(
id='erp5_login',
portal_type='ERP5 Login',
reference=self.user.getUserId(), # XXX workaround until https://lab.nexedi.com/nexedi/erp5/merge_requests/478
password=self.user_password
).validate()
self.user.validate()
self.tic()
# give this user some roles
for role in ('Assignee', 'Assignor', 'Auditor',):
self.portal.acl_users.zodb_roles.assignRoleToPrincipal(
role, self.user.getUserId())
self.tic()
self.login(self.user.getUserId())
def getWebSite(self):
return self.portal.web_site_module.erp5_officejs_support_request_ui
class TestSupportRequestCreateNewSupportRequest(SupportRequestTestCase):
def test_submit_support_request(self):
self.getWebSite().SupportRequestModule_createSupportRequest(
description='<b>Help !!!</b>',
file=None,
# FIXME: resource passed by the UI should be full relative URL
resource='erp5_officejs_support_request_ui_test_service_001',
# resource=self.portal.service_module.erp5_officejs_support_request_ui_test_service_001.getRelativeUrl(),
title=self.id(),
project='erp5_officejs_support_request_ui_test_project_001',
# FIXME: project passed by the UI should be full relative URL
#project=self.portal.project_module.erp5_officejs_support_request_ui_test_project_001.getRelativeUrl()
source_reference='xxx-message-id'
)
# this creates synchronoulsy a support request
support_request, = [sr for sr in self.portal.support_request_module.contentValues()
if sr.getTitle() == self.id()]
# the API to get comments works before ingestion, thanks to portal_session
self.assertEqual(
[dict(
user=self.user.getTitle(),
text='<b>Help !!!</b>',
attachment_link=None,
attachment_name=None,
message_id='xxx-message-id'),],
ignoreKeys(
json.loads(support_request.SupportRequest_getCommentPostListAsJson()),
'date'))
# another activity will create a web message
self.tic()
self.assertEqual(
'submitted',
support_request.getSimulationState()
)
self.assertEqual(
self.portal.project_module.erp5_officejs_support_request_ui_test_project_001,
support_request.getSourceProjectValue()
)
self.assertEqual(
self.portal.service_module.erp5_officejs_support_request_ui_test_service_001,
support_request.getResourceValue()
)
self.assertEqual(self.user, support_request.getDestinationDecisionValue())
web_message, = support_request.getFollowUpRelatedValueList(
portal_type='Web Message'
)
self.assertEqual('stopped', web_message.getSimulationState())
self.assertEqual('<b>Help !!!</b>', web_message.asStrippedHTML())
self.assertEqual(self.user, web_message.getSourceValue())
self.assertIsNotNone(web_message.getResourceValue())
self.assertIsNotNone(web_message.getStartDate())
# there's a html post
post, = web_message.getAggregateValueList(
portal_type='HTML Post'
)
self.assertEqual('<b>Help !!!</b>', str(post.getData()))
# post have been archived once ingested
self.assertEqual('archived', post.getValidationState())
# the API to get comments works even after ingested
self.assertEqual(
[dict(
user=self.user.getTitle(),
text='<b>Help !!!</b>',
date=web_message.getStartDate().ISO8601(),
attachment_link=None,
attachment_name=None,
message_id='xxx-message-id'),],
json.loads(support_request.SupportRequest_getCommentPostListAsJson()))
def test_submit_support_request_with_attachment(self):
self.getWebSite().SupportRequestModule_createSupportRequest(
description='<b>Look at this file !</b>',
file=FileUpload("the text content"),
# FIXME: resource passed by the UI should be full relative URL
resource='erp5_officejs_support_request_ui_test_service_001',
# resource=self.portal.service_module.erp5_officejs_support_request_ui_test_service_001.getRelativeUrl(),
title=self.id(),
project='erp5_officejs_support_request_ui_test_project_001',
# FIXME: project passed by the UI should be full relative URL
#project=self.portal.project_module.erp5_officejs_support_request_ui_test_project_001.getRelativeUrl()
source_reference='xxx-message-id',
)
support_request, = [sr for sr in self.portal.support_request_module.contentValues()
if sr.getTitle() == self.id()]
# the API to get comments works before ingestion, thanks to portal_session
self.assertEqual(
[dict(
user=self.user.getTitle(),
text='<b>Look at this file !</b>',
attachment_name='attached_file.txt',
message_id='xxx-message-id'),],
ignoreKeys(
json.loads(support_request.SupportRequest_getCommentPostListAsJson()),
'date',
'attachment_link'))
self.tic()
web_message, = support_request.getFollowUpRelatedValueList(
portal_type='Web Message'
)
self.assertEqual('stopped', web_message.getSimulationState())
self.assertEqual('<b>Look at this file !</b>', web_message.asStrippedHTML())
# there's a html post
post, = web_message.getAggregateValueList(
portal_type='HTML Post'
)
self.assertEqual('<b>Look at this file !</b>', str(post.getData()))
file_post, = post.getSuccessorValueList()
self.assertEqual('the text content', str(file_post.getData()))
# a text was ingested from the file post
file_document, = web_message.getAggregateValueList(
portal_type='Text'
)
self.assertEqual('attached_file.txt', file_document.getFilename())
self.assertEqual('the text content', str(file_document.getData()))
self.assertEqual(
[dict(
user=self.user.getTitle(),
text='<b>Look at this file !</b>',
date=web_message.getStartDate().ISO8601(),
attachment_name='attached_file.txt',
attachment_link=file_document.getRelativeUrl(),
message_id='xxx-message-id')],
json.loads(support_request.SupportRequest_getCommentPostListAsJson()))
class TestSupportRequestCommentOnExistingSupportRequest(SupportRequestTestCase):
def test_comment_on_support_request(self):
support_request = self.portal.support_request_module.erp5_officejs_support_request_ui_test_support_reuqest_001
self.portal.PostModule_createHTMLPostForSupportRequest(
follow_up=support_request.getRelativeUrl(),
predecessor=None,
data="<p>Hello !</p>",
file=None,
source_reference="xxx-message-id",
web_site_relative_url=self.getWebSite().getRelativeUrl(),
)
# the API to get comments works before ingestion, thanks to portal_session
self.assertEqual(
[dict(
user=self.user.getTitle(),
text='<p>Hello !</p>',
attachment_link=None,
attachment_name=None,
message_id='xxx-message-id'),],
ignoreKeys(
json.loads(support_request.SupportRequest_getCommentPostListAsJson()),
'date'))
self.tic()
web_message, = support_request.getFollowUpRelatedValueList(
portal_type='Web Message'
)
self.assertEqual('<p>Hello !</p>', web_message.asStrippedHTML())
# the API to get comments also works once ingested
self.assertEqual(
[dict(
user=self.user.getTitle(),
text='<p>Hello !</p>',
date=web_message.getStartDate().ISO8601(),
attachment_link=None,
attachment_name=None,
message_id='xxx-message-id'),],
json.loads(support_request.SupportRequest_getCommentPostListAsJson()))
def test_comment_on_support_request_with_attachment(self):
support_request = self.portal.support_request_module.erp5_officejs_support_request_ui_test_support_reuqest_001
self.portal.PostModule_createHTMLPostForSupportRequest(
follow_up=support_request.getRelativeUrl(),
predecessor=None,
data="<p>Please look at the <b>attached file</b></p>",
file=FileUpload("the text content"),
source_reference="xxx-message-id",
web_site_relative_url=self.getWebSite().getRelativeUrl(),
)
# the API to get comments works before ingestion, thanks to portal_session
self.assertEqual(
[dict(
user=self.user.getTitle(),
text='<p>Please look at the <b>attached file</b></p>',
attachment_name='attached_file.txt',
message_id='xxx-message-id'),],
ignoreKeys(
json.loads(support_request.SupportRequest_getCommentPostListAsJson()),
'date', 'attachment_link'))
self.tic()
web_message, = support_request.getFollowUpRelatedValueList(
portal_type='Web Message'
)
self.assertEqual('<p>Please look at the <b>attached file</b></p>', web_message.asStrippedHTML())
# a text document was ingested from the file post
file_document, = web_message.getAggregateValueList(
portal_type='Text'
)
self.assertEqual('attached_file.txt', file_document.getFilename())
self.assertEqual('the text content', str(file_document.getData()))
self.assertEqual('shared', file_document.getValidationState())
# this document is also attached to the context of the support request
# and is visible from the document tab.
self.assertIn(
file_document,
[doc.getObject() for doc in support_request.Base_getRelatedDocumentList()])
# the API to get comments also works once ingested
self.assertEqual(
[dict(
user=self.user.getTitle(),
text='<p>Please look at the <b>attached file</b></p>',
date=web_message.getStartDate().ISO8601(),
attachment_link=file_document.getRelativeUrl(),
attachment_name='attached_file.txt',
message_id='xxx-message-id'),],
json.loads(support_request.SupportRequest_getCommentPostListAsJson()))
def test_html_escape(self):
support_request = self.portal.support_request_module.erp5_officejs_support_request_ui_test_support_reuqest_001
self.portal.PostModule_createHTMLPostForSupportRequest(
follow_up=support_request.getRelativeUrl(),
predecessor=None,
data="<p>look <script>alert('haha')</script></p>",
file=FileUpload("the text content"),
source_reference="xxx-message-id",
web_site_relative_url=self.getWebSite().getRelativeUrl(),
)
self.tic()
web_message, = support_request.getFollowUpRelatedValueList(
portal_type='Web Message'
)
post, = web_message.getAggregateValueList(portal_type='HTML Post')
# on the web message, the HTML is escaped for safety
self.assertEqual('<p>look </p>', web_message.getTextContent())
# but the post follow the "store what user entered as-is" rule.
# (so looking at posts can be dangerous)
self.assertEqual(
"<p>look <script>alert('haha')</script></p>",
str(post.getData()))
def test_support_request_comment_include_other_event_type(self):
support_request = self.portal.support_request_module.erp5_officejs_support_request_ui_test_support_reuqest_001
event = self.portal.event_module.newContent(
portal_type='Note',
source_value=self.user,
follow_up_value=support_request,
resource_value=self.portal.service_module.erp5_officejs_support_request_ui_test_service_001,
text_content="Notes from meeting...",
start_date=DateTime(2001, 1, 1),
)
event.start()
event.stop()
self.tic()
self.assertEqual(
[dict(
user=self.user.getTitle(),
text="Notes from meeting...",
date=DateTime(2001, 1, 1).ISO8601(),
attachment_link=None,
attachment_name=None,)],
ignoreKeys(json.loads(support_request.SupportRequest_getCommentPostListAsJson()), 'message_id'))
class TestSupportRequestRSS(SupportRequestTestCase):
# XXX token PAS plugin is not set up automatically when installing erp5_access_token
# so we set it up the same way test.erp5.testERP5AccessTokenSkins is setting it up
def _setupAccessTokenExtraction(self):
pas = self.portal.acl_users
access_extraction_list = [q for q in pas.objectValues() \
if q.meta_type == 'ERP5 Access Token Extraction Plugin']
if len(access_extraction_list) == 0:
dispacher = pas.manage_addProduct['ERP5Security']
dispacher.addERP5AccessTokenExtractionPlugin('token_login')
pas.token_login.manage_activateInterfaces(('IExtractionPlugin',))
elif len(access_extraction_list) > 1:
raise ValueError("Too many token extraction plugins")
self.tic()
def afterSetUp(self):
self._setupAccessTokenExtraction()
SupportRequestTestCase.afterSetUp(self)
self.attached_document = self.portal.document_module.newContent(
portal_type='Text',
filename='tmp.txt'
)
self.attached_document.publish()
self.support_request = self.portal.support_request_module.erp5_officejs_support_request_ui_test_support_reuqest_001
self.event = self.portal.event_module.newContent(
portal_type='Web Message',
source_value=self.user,
follow_up_value=self.support_request,
resource_value=self.portal.service_module.erp5_officejs_support_request_ui_test_service_001,
text_content="<p>This is <b>Content</b></p>",
start_date=DateTime(2001, 1, 1),
aggregate_value_list=(self.attached_document, )
)
self.event.start()
self.event.stop()
self.tic()
def _checkRSS(self, response):
self.assertEqual(httplib.OK, response.getStatus())
rss = feedparser.parse(response.getBody())
self.assertEqual(rss['feed']['title'], "Support Requests")
item, = rss.entries
self.assertEqual(item['author'], self.user.getTitle())
self.assertIn(self.support_request.getRelativeUrl(), item['link'])
self.assertEqual(item['published'], DateTime(2001, 1, 1).rfc822())
self.assertEqual(item['summary'], '<p>This is <b>Content</b></p>')
enclosure, = [link for link in item['links'] if link['rel'] == 'enclosure']
self.assertIn(
self.attached_document.getRelativeUrl(),
enclosure['href'])
# https://pythonhosted.org/feedparser/bozo.html#advanced-bozo
self.assertFalse(rss.bozo)
def test_RSS(self):
response = self.publish(
"%s/support_request_module/SupportRequestModule_viewLastSupportRequestListAsRss" % self.getWebSite().getPath(),
basic='%s:%s' % (self.user.erp5_login.getReference(), self.user_password))
self._checkRSS(response)
def test_RSS_with_token(self):
response = self.publish(
"%s/support_request_module/SupportRequestModule_generateRSSLinkAsJson" % self.getWebSite().getPath(),
basic='%s:%s' % (self.user.erp5_login.getReference(), self.user_password))
restricted_access_url = json.loads(response.getBody())['restricted_access_url']
# make it relative url
parsed_url = urlparse.urlparse(restricted_access_url)
restricted_access_url = restricted_access_url.replace(
'%s://%s' % (parsed_url.scheme, parsed_url.netloc), '', 1)
# and check it (this time the request is not basic-authenticated)
self._checkRSS(self.publish(restricted_access_url))
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Test Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_recorded_property_dict</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>testSupportRequest</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test.erp5.testSupportRequest</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Test Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.patches.WorkflowTool"/>
</pickle>
<pickle>
<tuple>
<none/>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</tuple>
</pickle>
</record>
</ZopeData>
erp5_ui_test_core erp5_ui_test_core
erp5_ui_test erp5_ui_test
erp5_web_renderjs_ui erp5_web_renderjs_ui
erp5_officejs_support_request_ui erp5_officejs_support_request_ui
\ No newline at end of file erp5_web_renderjs_ui_test
\ No newline at end of file
test.erp5.testFunctionalSupportRequest test.erp5.testFunctionalSupportRequest
\ No newline at end of file test.erp5.testSupportRequest
\ No newline at end of file
...@@ -73,6 +73,7 @@ ...@@ -73,6 +73,7 @@
<value> <value>
<list> <list>
<string>my_title</string> <string>my_title</string>
<string>my_source_reference</string>
</list> </list>
</value> </value>
</item> </item>
......
...@@ -2,13 +2,13 @@ ...@@ -2,13 +2,13 @@
<ZopeData> <ZopeData>
<record id="1" aka="AAAAAAAAAAE="> <record id="1" aka="AAAAAAAAAAE=">
<pickle> <pickle>
<global name="TextAreaField" module="Products.Formulator.StandardFields"/> <global name="StringField" module="Products.Formulator.StandardFields"/>
</pickle> </pickle>
<pickle> <pickle>
<dictionary> <dictionary>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>my_description</string> </value> <value> <string>my_source_reference</string> </value>
</item> </item>
<item> <item>
<key> <string>message_values</string> </key> <key> <string>message_values</string> </key>
...@@ -18,21 +18,13 @@ ...@@ -18,21 +18,13 @@
<key> <string>external_validator_failed</string> </key> <key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value> <value> <string>The input failed the external validator.</string> </value>
</item> </item>
<item>
<key> <string>line_too_long</string> </key>
<value> <string>A line was too long.</string> </value>
</item>
<item> <item>
<key> <string>required_not_found</string> </key> <key> <string>required_not_found</string> </key>
<value> <string>Please provide a description for this support request.</string> </value> <value> <string>Input is required but no input given.</string> </value>
</item> </item>
<item> <item>
<key> <string>too_long</string> </key> <key> <string>too_long</string> </key>
<value> <string>You entered too many characters.</string> </value> <value> <string>Too much input was given.</string> </value>
</item>
<item>
<key> <string>too_many_lines</string> </key>
<value> <string>You entered too many lines.</string> </value>
</item> </item>
</dictionary> </dictionary>
</value> </value>
...@@ -58,39 +50,39 @@ ...@@ -58,39 +50,39 @@
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>editable</string> </key> <key> <string>display_maxwidth</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>enabled</string> </key> <key> <string>display_width</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>external_validator</string> </key> <key> <string>editable</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>extra</string> </key> <key> <string>enabled</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>height</string> </key> <key> <string>external_validator</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>hidden</string> </key> <key> <string>extra</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>max_length</string> </key> <key> <string>hidden</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>max_linelength</string> </key> <key> <string>input_type</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>max_lines</string> </key> <key> <string>max_length</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
...@@ -102,15 +94,15 @@ ...@@ -102,15 +94,15 @@
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>unicode</string> </key> <key> <string>truncate</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>whitespace_preserve</string> </key> <key> <string>unicode</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>width</string> </key> <key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
</dictionary> </dictionary>
...@@ -137,39 +129,39 @@ ...@@ -137,39 +129,39 @@
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>editable</string> </key> <key> <string>display_maxwidth</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>enabled</string> </key> <key> <string>display_width</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>external_validator</string> </key> <key> <string>editable</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>extra</string> </key> <key> <string>enabled</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>height</string> </key> <key> <string>external_validator</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>hidden</string> </key> <key> <string>extra</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>max_length</string> </key> <key> <string>hidden</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>max_linelength</string> </key> <key> <string>input_type</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>max_lines</string> </key> <key> <string>max_length</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
...@@ -181,15 +173,15 @@ ...@@ -181,15 +173,15 @@
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>unicode</string> </key> <key> <string>truncate</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>whitespace_preserve</string> </key> <key> <string>unicode</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>width</string> </key> <key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
</dictionary> </dictionary>
...@@ -215,6 +207,14 @@ ...@@ -215,6 +207,14 @@
<key> <string>description</string> </key> <key> <string>description</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item>
<key> <string>display_maxwidth</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <int>20</int> </value>
</item>
<item> <item>
<key> <string>editable</string> </key> <key> <string>editable</string> </key>
<value> <int>1</int> </value> <value> <int>1</int> </value>
...@@ -231,45 +231,37 @@ ...@@ -231,45 +231,37 @@
<key> <string>extra</string> </key> <key> <string>extra</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item>
<key> <string>height</string> </key>
<value> <int>5</int> </value>
</item>
<item> <item>
<key> <string>hidden</string> </key> <key> <string>hidden</string> </key>
<value> <int>0</int> </value> <value> <int>0</int> </value>
</item> </item>
<item> <item>
<key> <string>max_length</string> </key> <key> <string>input_type</string> </key>
<value> <string></string> </value> <value> <string>text</string> </value>
</item> </item>
<item> <item>
<key> <string>max_linelength</string> </key> <key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_lines</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>required</string> </key> <key> <string>required</string> </key>
<value> <int>1</int> </value> <value> <int>0</int> </value>
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>title</string> </key>
<value> <string>my_description</string> </value> <value> <string>Source Reference</string> </value>
</item> </item>
<item> <item>
<key> <string>unicode</string> </key> <key> <string>truncate</string> </key>
<value> <int>0</int> </value> <value> <int>0</int> </value>
</item> </item>
<item> <item>
<key> <string>whitespace_preserve</string> </key> <key> <string>unicode</string> </key>
<value> <int>0</int> </value> <value> <int>0</int> </value>
</item> </item>
<item> <item>
<key> <string>width</string> </key> <key> <string>whitespace_preserve</string> </key>
<value> <int>40</int> </value> <value> <int>0</int> </value>
</item> </item>
</dictionary> </dictionary>
</value> </value>
......
# XXX do we need two scripts ??
portal = context.getPortalObject() portal = context.getPortalObject()
traverse = context.getPortalObject().restrictedTraverse
# create an HTML Post # create an HTML Post
post_module = portal.post_module post_module = portal.post_module
...@@ -6,14 +9,16 @@ post_module = portal.post_module ...@@ -6,14 +9,16 @@ post_module = portal.post_module
now = DateTime() now = DateTime()
post_edit_kw = { post_edit_kw = {
"start_date": now, "start_date": now,
"follow_up_value": context.getPortalObject().restrictedTraverse(follow_up), "follow_up_value": traverse(follow_up),
"text_content": data, "text_content": data,
"source_reference": source_reference,
"title": title,
} }
if predecessor not in (None, ""):
predecessor_value, = portal.portal_catalog(relative_url=predecessor, limit=2) if predecessor:
post_edit_kw["predecessor_value"] = predecessor_value.getObject() post_edit_kw["predecessor"] = traverse(predecessor)
post = post_module.newContent( post = post_module.newContent(
immediate_reindex=immediate_reindex_context_manager,
portal_type='HTML Post', portal_type='HTML Post',
**post_edit_kw **post_edit_kw
) )
......
...@@ -50,16 +50,7 @@ ...@@ -50,16 +50,7 @@
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>follow_up, data, predecessor=None, immediate_reindex_context_manager=None</string> </value> <value> <string>follow_up, data, predecessor=None, source_reference=None, title=None</string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
......
return context.PostModule_createHTMLPost( return context.PostModule_createHTMLPost(
title=data.splitlines()[0][:30] if data else None,
source_reference=source_reference,
data=data,
follow_up=follow_up, follow_up=follow_up,
predecessor=predecessor, predecessor=predecessor,
data="<p>" + data.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace(" ", " &nbsp;").replace("\n", "<br/>") + "</p>",
immediate_reindex_context_manager=immediate_reindex_context_manager,
) )
...@@ -50,16 +50,7 @@ ...@@ -50,16 +50,7 @@
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>follow_up, data, predecessor=None, immediate_reindex_context_manager=None</string> </value> <value> <string>follow_up, data, source_reference, predecessor=None</string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="StateDefinition" module="Products.DCWorkflow.States"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>description</string> </key>
<value> <string>When the post have been fully ingested, we archive it.</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>archived</string> </value>
</item>
<item>
<key> <string>permission_roles</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Archived</string> </value>
</item>
<item>
<key> <string>transitions</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>type_list</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>Access contents information</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>Modify portal content</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>View</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
</tuple>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -28,6 +28,8 @@ ...@@ -28,6 +28,8 @@
<key> <string>transitions</string> </key> <key> <string>transitions</string> </key>
<value> <value>
<tuple> <tuple>
<string>archive</string>
<string>archive_action</string>
<string>hide</string> <string>hide</string>
<string>hide_action</string> <string>hide_action</string>
</tuple> </tuple>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="TransitionDefinition" module="Products.DCWorkflow.Transitions"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>archive</string> </value>
</item>
<item>
<key> <string>new_state_id</string> </key>
<value> <string>archived</string> </value>
</item>
<item>
<key> <string>script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Archive Post</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Guard" module="Products.DCWorkflow.Guard"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>roles</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Owner</string>
</tuple>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="TransitionDefinition" module="Products.DCWorkflow.Transitions"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string>Archive Post</string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<value> <string>%(content_url)s/Base_viewWorkflowActionDialog?workflow_action=archive_action</string> </value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>archive_action</string> </value>
</item>
<item>
<key> <string>new_state_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Archive Post (Action)</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Guard" module="Products.DCWorkflow.Guard"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>roles</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
</tuple>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
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