Author: douglas
Date: 2008-01-02 12:03:48 -0500 (Wed, 02 Jan 2008)
New Revision: 12227
Added:
opencore/trunk/opencore/listen/browser/create.pt
opencore/trunk/opencore/listen/browser/edit.pt
Modified:
opencore/trunk/opencore/browser/octopus.py
opencore/trunk/opencore/listen/browser/configure.zcml
opencore/trunk/opencore/listen/browser/listen_formlib.pt
opencore/trunk/opencore/listen/browser/mailing_lists.pt
opencore/trunk/opencore/listen/browser/moderation.pt
opencore/trunk/opencore/listen/browser/view.py
opencore/trunk/opencore/listen/utils.py
Log:
Merge from listen-without-formlib branch, tests passing :-D
Modified: opencore/trunk/opencore/browser/octopus.py
===================================================================
--- opencore/trunk/opencore/browser/octopus.py 2008-01-02 17:02:19 UTC (rev
12226)
+++ opencore/trunk/opencore/browser/octopus.py 2008-01-02 17:03:48 UTC (rev
12227)
@@ -125,9 +125,22 @@
def __delegate(self, action, objects, fields, raise_=False):
""" delegate to the appropriate action method, if it exists."""
- if action in self.actions:
- return self.actions[action](self, objects, fields)
- elif raise_:
+
+ #check self and superclasses for appropriate action methods
+ bases = [self.__class__]
+ while bases:
+ base = bases[0]
+ if hasattr(base, 'actions'):
+ try:
+ if action in base.actions:
+ return base.actions[action](self, objects, fields)
+ except TypeError: #actions isn't a list
+ pass
+
+ bases = bases[1:]
+ bases += list(base.__bases__)
+
+ if raise_:
raise KeyError("No actions in request")
elif self.actions.default is not None:
return self.actions.default(self, objects, fields)
Modified: opencore/trunk/opencore/listen/browser/configure.zcml
===================================================================
--- opencore/trunk/opencore/listen/browser/configure.zcml 2008-01-02
17:02:19 UTC (rev 12226)
+++ opencore/trunk/opencore/listen/browser/configure.zcml 2008-01-02
17:03:48 UTC (rev 12227)
@@ -17,19 +17,19 @@
<include package="plone.app.form" />
<browser:page
- name="opencore.add_mailinglist"
+ name="create"
for="opencore.featurelets.interfaces.IListenContainer"
permission="listen.AddMailingList"
- class=".view.NuiMailingListAddView"
- template="listen_formlib.pt"
+ class=".view.ListAddView"
+ template="create.pt"
/>
<browser:page
name="edit"
for="opencore.listen.interfaces.IOpenMailingList"
permission="listen.EditMailingList"
- class=".view.NuiMailingListEditView"
- template="listen_formlib.pt"
+ class=".view.ListEditView"
+ template="edit.pt"
/>
<browser:page
@@ -40,13 +40,6 @@
template="listen_macros.pt"
/>
- <adapter
- for=".view.NuiMailingListAddView"
- factory=".view.default_named_template_adapter"
- name="default"
- provides="zope.formlib.namedtemplate.INamedTemplate"
- />
-
<browser:page
for="opencore.listen.interfaces.IOpenMailingList"
name="summary"
Copied: opencore/trunk/opencore/listen/browser/create.pt (from rev 12156,
opencore/branches/listen-without-formlib/opencore/listen/browser/create.pt)
===================================================================
--- opencore/trunk/opencore/listen/browser/create.pt
(rev 0)
+++ opencore/trunk/opencore/listen/browser/create.pt 2008-01-02 17:03:48 UTC
(rev 12227)
@@ -0,0 +1,167 @@
+<html metal:use-macro="context/@@standard_macros/master">
+ <head>
+ <title metal:fill-slot="title"
tal:content="string:${view/context/Title} - ${view/area/Title}" />
+ </head>
+ <body>
+ <div metal:fill-slot="content">
+ <div class="oc-headingBlock">
+ <h1 i18n:translate="create_list_heading">Create
new mailing list</h1>
+ <p i18n:translate="create_list_desc"
class="oc-headingContext">Email based mailing lists are used to have
discussions and distribute information about the project.</p>
+ </div>
+
+ <div id="oc-content-main"
class="oc-content-main-fullWidth">
+ <form name="edit-form" id="oc-list-create"
method="post" enctype="multipart/form-data"
+ class="enableUnloadProtection"
tal:attributes="action view/name">
+
+<!-- basics -->
+ <fieldset class="oc-boxy">
+ <legend
i18n:translate="create_list_step_1" class="oc-legend-heading
oc-biggerText">Step 1: Basics</legend>
+ <table class="oc-form">
+ <tbody>
+ <tr
class="oc-form-row">
+ <th
class="oc-form-label" scope="row">
+
<label i18n:translate="list_title_name" for="title">Title</label>
+ </th>
+ <td
class="oc-form-value">
+
<input type="text" id="title" class="oc-autoFocus" name="title"
+
tal:attributes="value request/title | nothing" />
+ </td>
+ <td
class="oc-form-help">
+
<span class="oc-form-context"></span>
+
<span id="oc-title-validator" class="oc-form-validator"></span>
+
<span id="oc-title-error" class="oc-form-error" tal:content="view/errors/title
| nothing" />
+ </td>
+ </tr>
+ <tr
class="oc-form-row">
+ <th
class="oc-form-label" scope="row">
+
<label i18n:translate="list_description_name"
for="description">Description</label>
+ </th>
+ <td
class="oc-form-value">
+
<textarea name="description" id="description" rows="3" cols="40"
tal:content="request/description | nothing"/>
+ </td>
+ <td
class="oc-form-help">
+
<span class="oc-form-context"></span>
+
<span id="oc-description-validator" class="oc-form-validator"></span>
+
<span id="oc-description-error" class="oc-form-error"
tal:content="view/errors/description | nothing" />
+ </td>
+ </tr>
+ <tr
class="oc-form-row">
+ <th
class="oc-form-label" scope="row">
+
<label i18n:translate="list_mailto_name" for="mailto">List Address
Prefix</label>
+ </th>
+ <td
class="oc-form-value oc-form-fieldBlock">
+
<input type="text" name="mailto" id="mailto"
+
class="oc-js-liveValidate"
+
tal:attributes="value request/mailto | nothing" />
+
<span tal:replace="view/getSuffix" />
+ </td>
+ <td
class="oc-form-help">
+
<span class="oc-form-context"></span>
+
<span id="oc-mailto-validator" class="oc-form-validator"></span>
+
<span id="oc-mailto-error" class="oc-form-error"
tal:content="view/errors/mailto | nothing" />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </fieldset>
+
+<!-- security -->
+ <fieldset class="oc-boxy">
+ <legend
i18n:translate="create_list_step_2" class="oc-legend-heading
oc-biggerText">Step 2: Security</legend>
+ <table class="oc-form">
+ <tbody>
+ <tr
class="oc-form-row">
+ <th
class="oc-form-label" scope="row">
+
<p i18n:translate="list_workflow_policy_name">Workflow</p>
+ </th>
+ <td
class="oc-form-value">
+
<p i18n:translate="list_workflow_desc" class="oc-headingContext
oc-smallText">The policy that defines the behavior of the list.</p>
+
<fieldset tal:define="policy request/workflow_policy | string:policy_open">
+
<ul class="oc-plainList oc-form-fieldBlock">
+
<li>
+
<input type="radio" class="oc-input-typeRadio"
id="workflow_policy_open" name="workflow_policy"
+
value="policy_open" tal:attributes="checked
python:policy == 'policy_open' and 'checked' or ''" />
+
<label i18n:translate="list_workflow_open"
for="workflow_policy_open">
+
Anyone who confirms their email address is valid can
post and receive messages.</label>
+
</li>
+
<li>
+
<input type="radio" class="oc-input-typeRadio"
id="workflow_policy_moderated" name="workflow_policy"
+
value="policy_moderated" tal:attributes="checked
python:policy == 'policy_moderated' and 'checked' or ''" />
+
<label i18n:translate="list_workflow_moderated"
for="workflow_policy_moderated">Anyone can receive messages, but each posted
message has to be approved by the list managers first.</label>
+
</li>
+
<li>
+
<input type="radio" class="oc-input-typeRadio"
id="workflow_policy_closed" name="workflow_policy"
+
value="policy_closed" tal:attributes="checked
python:policy == 'policy_closed' and 'checked' or ''" />
+
<label i18n:translate="list_workflow_closed"
for="workflow_policy_closed">Only those approved by the list managers can post
and receive messages.</label>
+
</li>
+
</ul>
+
</fieldset>
+ </td>
+ <td
class="oc-form-help">
+
<span class="oc-form-context"></span>
+ </td>
+ </tr>
+<!-- managers -->
+ <tr
class="oc-form-row">
+ <th
class="oc-form-label" scope="row">
+
<label i18n:translate="list_managers_name" for="managers">Managers</label>
+ </th>
+ <td
class="oc-form-value">
+
<p i18n:translate="list_managers_desc" class="oc-headingContext oc-smallText
oc-js-memberList_description">A comma separated list of users with permissions
to modify the list.</p>
+
<input type="text" id="managers" class="oc-autoFocus oc-js-memberList"
name="managers"
+
tal:attributes="value request/managers | view/loggedinmember | nothing"
/>
+ </td>
+ <td
class="oc-form-help">
+
<span class="oc-form-context"></span>
+
<span id="oc-managers-validator" class="oc-form-validator"></span>
+
<span id="oc-managers-error" class="oc-form-error"
tal:content="view/errors/managers | nothing" />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </fieldset>
+<!-- archival -->
+ <fieldset class="oc-boxy">
+ <legend
i18n:translate="create_list_step_3" class="oc-legend-heading
oc-biggerText">Step 3: Archival</legend>
+ <p
i18n:translate="create_list_step_3_desc" class="oc-headingContext
oc-smallText">When archiving is enabled, all messages sent to the list will be
saved on the server. You may choose whether to archive just the message text,
or include attachments.</p>
+ <fieldset
tal:define="policy_str request/archival_policy | python:0;
+ policy
python: int(policy_str)">
+ <ul
class="oc-form-radiolist oc-form-fieldBlock">
+ <li>
+ <input
type="radio" class="oc-input-typeRadio" id="archival_policy_all"
name="archival_policy"
+
value="0" tal:attributes="checked python:policy == 0 and 'checked' or ''" />
+ <label
i18n:translate="list_archive_all" for="archival_policy_all">The entire message,
including attachments</label>
+ </li>
+ <li>
+ <input
type="radio" class="oc-input-typeRadio" id="archival_policy_text"
name="archival_policy"
+
value="1" tal:attributes="checked python:policy == 1 and 'checked' or ''" />
+ <label
i18n:translate="list_archive_text" for="archival_policy_text">The message text
only</label>
+ </li>
+ <li>
+ <input
type="radio" class="oc-input-typeRadio" id="archival_policy_none"
name="archival_policy"
+
value="2" tal:attributes="checked python:policy == 2 and 'checked' or ''" />
+ <label
i18n:translate="list_archive_none" for="archival_policy_none">Do not archive
messages</label>
+ </li>
+ </ul>
+ </fieldset>
+ </fieldset>
+
+<!-- submit -->
+ <fieldset>
+ <ul class="oc-actions">
+ <li>
+ <input
type="submit"
+
name="task|add"
+
value="Create"
+
i18n:attributes="value create_create_button"
+
class="oc-button oc-chooseThis" />
+ </li>
+ <li>or <a
i18n:translate="list_cancel" href="">Cancel</a></li>
+ </ul>
+ </fieldset>
+ </form>
+ </div>
+ </div>
+ </body>
+</html>
+
Copied: opencore/trunk/opencore/listen/browser/edit.pt (from rev 12156,
opencore/branches/listen-without-formlib/opencore/listen/browser/edit.pt)
===================================================================
--- opencore/trunk/opencore/listen/browser/edit.pt
(rev 0)
+++ opencore/trunk/opencore/listen/browser/edit.pt 2008-01-02 17:03:48 UTC
(rev 12227)
@@ -0,0 +1,162 @@
+<html metal:use-macro="context/@@standard_macros/master">
+ <head>
+ <title metal:fill-slot="title"
tal:content="string:${view/context/Title} - ${view/area/Title}" />
+ </head>
+ <body>
+
+ <div metal:fill-slot="content">
+ <div metal:use-macro="here/@@listen_macros/bcrumb"/>
+ <div metal:use-macro="here/@@listen_macros/tabs"/>
+
+ <div id="oc-content-main"
class="oc-content-main-fullWidth">
+ <form name="edit-form" id="oc-list-edit"
method="post" enctype="multipart/form-data"
+ class="enableUnloadProtection"
tal:attributes="action view/name">
+<!-- basics -->
+ <fieldset class="oc-boxy">
+ <legend
i18n:translate="edit_list_basics" class="oc-legend-heading
oc-biggerText">Basics</legend>
+ <table class="oc-form">
+ <tbody>
+ <tr
class="oc-form-row">
+ <th
class="oc-form-label" scope="row">
+
<label i18n:translate="list_title_name" for="title">Title</label>
+ </th>
+ <td
class="oc-form-value">
+
<input type="text" id="title" class="oc-autoFocus" name="title"
+
tal:attributes="value request/title | context/title | nothing" />
+ </td>
+ <td
class="oc-form-help">
+
<span class="oc-form-context"></span>
+
<span id="oc-title-validator" class="oc-form-validator"></span>
+
<span id="oc-title-error" class="oc-form-error" tal:content="view/errors/title
| nothing" />
+ </td>
+ </tr>
+ <tr
class="oc-form-row">
+ <th
class="oc-form-label" scope="row">
+
<label i18n:translate="list_description_name"
for="description">Description</label>
+ </th>
+ <td
class="oc-form-value">
+
<textarea name="description" id="description" rows="3" cols="40"
tal:content="request/description | context/description | nothing"/>
+ </td>
+ <td
class="oc-form-help">
+
<span class="oc-form-context"></span>
+
<span id="oc-description-validator" class="oc-form-validator"></span>
+
<span id="oc-description-error" class="oc-form-error"
tal:content="view/errors/description | nothing" />
+ </td>
+ </tr>
+ <tr
class="oc-form-row">
+ <th
class="oc-form-label" scope="row">
+
<label i18n:translate="list_mailto_name" for="mailto">List Address
Prefix</label>
+ </th>
+ <td
class="oc-form-value oc-form-fieldBlock">
+
<span tal:replace="context/mailto" />
+
+ </td>
+ <td
class="oc-form-help">
+
<span class="oc-form-context"></span>
+
<span id="oc-mailto-validator" class="oc-form-validator"></span>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </fieldset>
+
+<!-- security -->
+ <fieldset class="oc-boxy">
+ <legend
i18n:translate="edit_list_security" class="oc-legend-heading
oc-biggerText">Security</legend>
+ <table class="oc-form">
+ <tbody>
+ <tr
class="oc-form-row">
+ <th
class="oc-form-label" scope="row">
+
<p i18n:translate="list_workflow_policy_name">Workflow</p>
+ </th>
+ <td
class="oc-form-value">
+
<p i18n:translate="list_workflow_desc" class="oc-headingContext
oc-smallText">The policy that defines the behavior of the list.</p>
+
<fieldset tal:define="policy request/workflow_policy | view/workflow_policy |
string:policy_open">
+
<ul class="oc-plainList oc-form-fieldBlock">
+
<li>
+
<input type="radio" class="oc-input-typeRadio"
id="workflow_policy_open" name="workflow_policy"
+
value="policy_open" tal:attributes="checked
python:policy == 'policy_open' and 'checked' or ''" />
+
<label i18n:translate="list_workflow_open"
for="workflow_policy_open">
+
Anyone who confirms their email address is valid can
post and receive messages.</label>
+
</li>
+
<li>
+
<input type="radio" class="oc-input-typeRadio"
id="workflow_policy_moderated" name="workflow_policy"
+
value="policy_moderated" tal:attributes="checked
python:policy == 'policy_moderated' and 'checked' or ''" />
+
<label i18n:translate="list_workflow_moderated"
for="workflow_policy_moderated">Anyone can receive messages, but each posted
message has to be approved by the list managers first.</label>
+
</li>
+
<li>
+
<input type="radio" class="oc-input-typeRadio"
id="workflow_policy_closed" name="workflow_policy"
+
value="policy_closed" tal:attributes="checked
python:policy == 'policy_closed' and 'checked' or ''" />
+
<label i18n:translate="list_workflow_closed"
for="workflow_policy_closed">Only those approved by the list managers can post
and receive messages.</label>
+
</li>
+
</ul>
+
</fieldset>
+ </td>
+ <td
class="oc-form-help">
+
<span class="oc-form-context"></span>
+ </td>
+ </tr>
+<!-- managers -->
+ <tr
class="oc-form-row">
+ <th
class="oc-form-label" scope="row">
+
<label i18n:translate="list_managers_name" for="managers">Managers</label>
+ </th>
+ <td
class="oc-form-value">
+
<p i18n:translate="list_managers_desc" class="oc-headingContext oc-smallText
oc-js-memberList_description">A comma separated list of users with permissions
to modify the list.</p>
+
<input type="text" id="managers" class="oc-autoFocus oc-js-memberList"
name="managers"
+
tal:attributes="value request/managers |
python:','.join(context.managers); | nothing" />
+ </td>
+ <td
class="oc-form-help">
+
<span class="oc-form-context"></span>
+
<span id="oc-managers-validator" class="oc-form-validator"></span>
+
<span id="oc-managers-error" class="oc-form-error"
tal:content="view/errors/managers | nothing" />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </fieldset>
+<!-- archival -->
+
+ <fieldset class="oc-boxy">
+ <legend
i18n:translate="list_archival" class="oc-legend-heading
oc-biggerText">Archival</legend>
+ <p
i18n:translate="list_archival_desc" class="oc-headingContext oc-smallText">When
archiving is enabled, all messages sent to the list will be saved on the
server. You may choose whether to archive just the message text, or include
attachments.</p>
+ <fieldset
tal:define="policy_str request/archival_policy | context/archived | python: 0;
+ policy
python:int(policy_str)">
+ <ul
class="oc-form-radiolist oc-form-fieldBlock">
+ <li>
+ <input
type="radio" class="oc-input-typeRadio" id="archival_policy_all"
name="archival_policy"
+
value="0" tal:attributes="checked python:policy == 0 and 'checked' or ''" />
+ <label
i18n:translate="list_archive_all" for="archival_policy_all">The entire message,
including attachments</label>
+ </li>
+ <li>
+ <input
type="radio" class="oc-input-typeRadio" id="archival_policy_text"
name="archival_policy"
+
value="1" tal:attributes="checked python:policy == 1 and 'checked' or ''" />
+ <label
i18n:translate="list_archive_text" for="archival_policy_text">The message text
only</label>
+ </li>
+ <li>
+ <input
type="radio" class="oc-input-typeRadio" id="archival_policy_none"
name="archival_policy"
+
value="2" tal:attributes="checked python:policy == 2 and 'checked' or ''" />
+ <label
i18n:translate="list_archive_none" for="archival_policy_none">Do not archive
messages</label>
+ </li>
+ </ul>
+ </fieldset>
+ </fieldset>
+
+<!-- submit -->
+ <fieldset>
+ <ul class="oc-actions">
+ <li>
+ <input
type="submit"
+
name="task|edit"
+
value="Save"
+
i18n:attributes="value edit_edit_button"
+
class="oc-button oc-chooseThis" />
+ </li>
+ <li>or <a
i18n:translate="edit_list_cancel" href="">Cancel</a></li>
+ </ul>
+ </fieldset>
+ </form>
+ </div>
+ </div>
+ </body>
+</html>
Modified: opencore/trunk/opencore/listen/browser/listen_formlib.pt
===================================================================
--- opencore/trunk/opencore/listen/browser/listen_formlib.pt 2008-01-02
17:02:19 UTC (rev 12226)
+++ opencore/trunk/opencore/listen/browser/listen_formlib.pt 2008-01-02
17:03:48 UTC (rev 12227)
@@ -26,7 +26,7 @@
tal:content="structure widget/error"
class="oc-form-error" />
<div class="oc-form-fieldBlock">
- <input tal:replace="structure widget" />
+ <input tal:attributes="value python:widget.hasValidInput() and
widget.getInputValue() or None" tal:replace="structure widget" />
</div>
</fieldset>
<span class="oc-actions"
Modified: opencore/trunk/opencore/listen/browser/mailing_lists.pt
===================================================================
--- opencore/trunk/opencore/listen/browser/mailing_lists.pt 2008-01-02
17:02:19 UTC (rev 12226)
+++ opencore/trunk/opencore/listen/browser/mailing_lists.pt 2008-01-02
17:03:48 UTC (rev 12227)
@@ -15,7 +15,7 @@
<div class="oc-headingBlock">
<h1 i18n:translate="mailing_lists_heading">Mailing lists</h1>
<p i18n:translate="mailing_lists_description"
class="oc-headingContent">
- View<span tal:condition="can_add_lists"> or manage
exisiting</span> mailing lists<span tal:condition="can_add_lists"> or <a
tal:attributes="href
string:${context/absolute_url}/opencore.add_mailinglist">add a new mailing
list</a></span>.
+ View<span tal:condition="can_add_lists"> or manage
exisiting</span> mailing lists<span tal:condition="can_add_lists"> or <a
tal:attributes="href string:${context/absolute_url}/create">add a new mailing
list</a></span>.
</p>
</div>
@@ -104,7 +104,7 @@
<div id="oc-content-sidebar">
<div class="oc-getstarted">
- <a class="oc-banana" tal:attributes="href
string:${context/absolute_url}/opencore.add_mailinglist">Add a mailing list</a>
+ <a class="oc-banana" tal:attributes="href
string:${context/absolute_url}/create">Add a mailing list</a>
</div>
</div>
</div>
Modified: opencore/trunk/opencore/listen/browser/moderation.pt
===================================================================
--- opencore/trunk/opencore/listen/browser/moderation.pt 2008-01-02
17:02:19 UTC (rev 12226)
+++ opencore/trunk/opencore/listen/browser/moderation.pt 2008-01-02
17:03:48 UTC (rev 12227)
@@ -26,7 +26,7 @@
</tr>
</thead>
<tbody>
- <tr tal:repeat="post pending_list">
+ <tr tal:repeat="post pending_list" tal:attributes="id
string:post_${post/postid}">
<td style="width: 25%;" tal:content="post/user_name" />
<td style="width: 49%;">
<dl class="oc-plainList">
@@ -43,22 +43,23 @@
<input type="hidden" name="email" tal:attributes="value
python: str(post['user'])" />
<ul class="oc-actions oc-dataTable-row-actions
oc-js-liveEdit-value">
<li>
- <input class="oc-actionLink" type="submit"
name="post_approve" i18n:attributes="value moderation_approve_button"
value="Approve" title="Allow this message to be posted" />
+ <input type="submit" name="post_approve"
class="oc-actionLink oc-actionButton oc-js-actionButton" tal:attributes="href
string:moderation?post_approve=Approve&postid=${post/postid}&email=${post/user}"
title="Allow this message to be posted" i18n:attributes="value
moderation_approve_button" value="Approve" />
</li>
<li>
- <input class="oc-actionLink" type="submit"
name="post_discard" i18n:attributes="value moderation_discard_button"
value="Discard (Spam)" title="Deny this message without notifying the sender" />
+ <input type="submit" name="post_discard"
class="oc-actionLink oc-actionButton oc-js-actionButton" tal:attributes="href
string:
moderation?post_discard=moderation_discard_button&postid=${post/postid}&email=${post/user}"
title="Deny this message without notifying the sender" i18n:attributes="value
moderation_discard_button" value="Discard" />
</li>
- <li class="oc-js-liveEdit_showForm"
tal:condition="python:is_post_moderated or is_membership_moderated">
- <a class="oc-actionLink" href="moderation#"
title="Deny this message and notify the sender, message optional"
i18n:attributes="value moderation_reject_button">Reject</a>
+ <li class="" tal:condition="python:is_post_moderated
or is_membership_moderated">
+ <input type="submit" name="post_reject"
class="oc-actionLink oc-actionButton oc-js-actionButton
oc-js-liveEdit_showForm" tal:attributes="href string:
moderation?post_deny=moderation_deny_button&postid=${post/postid}&email=${post/user}"
title="Deny this message and notify the sender, message optional"
i18n:attributes="value moderation_reject_confirm_button" value="Reject" />
</li>
</ul>
- <div class="oc-js-liveEdit-editForm oc-liveEdit-editForm
oc-form-fieldBlock" style="display: none;"
tal:condition="python:is_post_moderated or is_membership_moderated">
+ <div class="oc-js-liveEdit-editForm oc-liveEdit-editForm
oc-form-fieldBlock" tal:condition="python:is_post_moderated or
is_membership_moderated">
<label class="oc-smallText"
for="message_reject_reason" i18n:translate="moderation_reason">
<strong>Reason:</strong> <span
class="oc-discreetText">(optional)</span>
</label>
<input id="message_reject_reason"
name="reject_reason" type="text" />
- <input class="oc-actionLink" type="submit"
name="post_reject" i18n:attributes="value moderation_reject_confirm_button"
value="Reject" title="Deny this message and notify the sender, message
optional"/> or <a href="#" class="oc-js-liveEdit_hideForm">Cancel</a>
+ <input class="oc-actionLink oc-js-actionButton"
name="post_reject" i18n:attributes="value moderation_reject_confirm_button"
type="submit" value="Reject" title="Deny this message and notify the sender,
message optional"/> or <a href="#" class="oc-js-liveEdit_hideForm">Cancel</a>
+
</div>
</form>
</td>
@@ -88,19 +89,20 @@
<tr tal:repeat="pending_member pending_members">
<td tal:content="pending_member/user_name" />
<td>
- <form name="moderate-member-form" id="moderate-member-form"
+ <form name="moderate-member-form"
id="moderate-member-form" method="POST"
+ class="oc-listen-moderate-form"
tal:attributes="action request/ACTUAL_URL">
<input type="hidden" name="email" tal:attributes="value
pending_member/user" />
<ul class="oc-actions oc-dataTable-row-actions">
<li>
- <input i18n:attributes="value
moderation_user_approve" type="submit" name="member_approve" value="Approve" />
+ <input class="oc-actionLink" i18n:attributes="value
moderation_user_approve" type="submit" name="member_approve" value="Approve" />
</li>
<li>
- <input i18n:attributes="value
moderation_user_discard" type="submit" name="member_discard" value="Discard" />
+ <input class="oc-actionLink" i18n:attributes="value
moderation_user_discard" type="submit" name="member_discard" value="Discard" />
</li>
<li oc-form-fieldBlock>
<label for="user_reject_reason">
- <input i18n:attributes="value
moderation_user_reject" type="submit" name="member_reject" value="Reject" />
+ <input class="oc-actionLink"
i18n:attributes="value moderation_user_reject" type="submit"
name="member_reject" value="Reject" />
</label>
<input type="text" id="user_reject_reason"
name="reject_reason" />
</li>
Modified: opencore/trunk/opencore/listen/browser/view.py
===================================================================
--- opencore/trunk/opencore/listen/browser/view.py 2008-01-02 17:02:19 UTC
(rev 12226)
+++ opencore/trunk/opencore/listen/browser/view.py 2008-01-02 17:03:48 UTC
(rev 12227)
@@ -1,28 +1,59 @@
-from Products.Five.browser import metaconfigure
-from Products.Five.browser import pagetemplatefile
from Acquisition import aq_inner
-from zope.app.annotation.interfaces import IAnnotations
+from Products.CMFCore.utils import getToolByName
+from Products.Five.browser import metaconfigure, pagetemplatefile
+from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
from Products.PageTemplates.PageTemplate import PageTemplate
-from Products.listen.browser.mail_archive_views import ArchiveForumView,
ArchiveDateView
-from Products.listen.browser.mail_archive_views import ArchiveNewTopicView,
SubFolderDateView
-from Products.listen.browser.mail_archive_views import ArchiveSearchView
-from Products.listen.browser.mail_message_views import ForumMailMessageView,
ThreadedMailMessageView
-from Products.listen.browser.mail_message_views import MessageReplyView,
SearchDebugView
+from Products.listen.browser.mail_archive_views import ArchiveForumView,
ArchiveDateView, \
+ ArchiveNewTopicView,
SubFolderDateView, \
+ ArchiveSearchView
+from Products.listen.browser.mail_message_views import ForumMailMessageView,
ThreadedMailMessageView, \
+ MessageReplyView,
SearchDebugView
from Products.listen.browser.manage_membership import ManageMembersView
-from Products.listen.browser.moderation import ModerationView
-from Products.listen.interfaces import IMailingList
+from Products.listen.browser.moderation import ModerationView as
BaseModerationView
+from Products.listen.config import PROJECTNAME
+from Products.listen.config import MODERATION_FAILED
+from Products.listen.content import ListTypeChanged
+from Products.listen.interfaces.list_types import PublicListTypeDefinition, \
+
PostModeratedListTypeDefinition, \
+
MembershipModeratedListTypeDefinition
+from Products.listen.interfaces import IMailingList, IMembershipModeratedList,
\
+ IPostModeratedList, IPublicList, \
+ IWriteMembershipList, IEmailPostPolicy,
\
+ IUserEmailMembershipPolicy
+
from Products.listen.utilities.list_lookup import ListLookupView
from lxml.html.clean import Cleaner
-from opencore.browser.base import BaseView
+from opencore.browser.formhandler import OctopoLite, action
+from opencore.browser.base import BaseView, _
+from opencore.listen.mailinglist import OpenMailingList
from opencore.listen.mailinglist_views import MailingListAddForm,
MailingListEditForm, MailingListView
+from opencore.listen.utils import isValidPrefix
from plone.app.form import _named
from plone.memoize.view import memoize as req_memoize
+from zope.app.annotation.interfaces import IAnnotations
+from zope.app.component.hooks import getSite
+from zope.event import notify
from zope.formlib.namedtemplate import INamedTemplate
-from zope.interface import implements
+from zope.interface import implements, directlyProvides
+from zExceptions import BadRequest
import cgi
+import re
import new
import os.path
+_ml_type_to_workflow = {
+ PublicListTypeDefinition : 'policy_open',
+ PostModeratedListTypeDefinition : 'policy_moderated',
+ MembershipModeratedListTypeDefinition : 'policy_closed',
+ }
+
+_workflow_to_ml_type = dict((y, x) for x, y in _ml_type_to_workflow.items())
+
+_list_error_fields = ['title', 'mailto']
+def oc_json_error(v):
+ return {'html': v,
+ 'action': 'copy',
+ }
class ListenBaseView(BaseView):
@req_memoize
def list_url(self):
@@ -65,6 +96,235 @@
return msgs
+ def getSuffix(self):
+ """
+ Retrieves the FQDN that is the list address suffix for a site from
+ the opencore_properties PropertySheet. Requires a context object
+ from inside the site so the properties tool can be retrieved.
+ """
+ # use threadlocal site to hook into acquisition context
+ site = getSite()
+ ptool = getToolByName(site, 'portal_properties')
+ ocprops = ptool._getOb('opencore_properties')
+ return '@' + str(ocprops.getProperty('mailing_list_fqdn').strip())
+
+
+class ListenEditBaseView(ListenBaseView, OctopoLite):
+
+ def validate_form(self, justValidate=False, creation=False):
+ putils = getToolByName(self.context, 'plone_utils')
+ # Create an empty dictionary to hold any eventual errors.
+ self.errors = {}
+
+ # Let's do some form validation
+ # Get and clean up title from request
+ title = self.request.form.get('title', '')
+ title = re.compile('\s+').sub(' ', title).strip()
+ # The form title variable must be unicode or listen blows up
+ if not isinstance(title, unicode):
+ title = unicode(title, 'utf-8')
+
+ if title:
+ self.request.form['title'] = title
+ else:
+ self.errors['title'] = _(u'list_invalid_title', u'The mailing list
must have a title.')
+
+ # Check the list of managers
+ form_managers = self.request.form.get('managers','')
+ if not isinstance(form_managers, unicode):
+ form_managers = unicode(form_managers, 'utf-8')
+ self.request.form['managers'] = form_managers
+
+ managers = []
+ bad_managers = []
+ if not form_managers:
+ self.errors['managers'] = _(u'list_no_managers_error', u'The
mailing list must have at least one manager.')
+ else:
+ for manager in form_managers.split(','):
+ manager = manager.strip()
+ if not self.is_member(manager):
+ bad_managers.append(manager)
+ else:
+ managers.append(manager)
+
+ if bad_managers:
+ s_message_mapping = {'managers': ", ".join(bad_managers)}
+ self.errors['managers'] = _(u'list_invalid_managers_error',
+ u'The following managers are not
members of this site: ${managers}',
+ mapping=s_message_mapping)
+
+ # Get and check the list policies
+ workflow = self.request.form.get('workflow_policy')
+ if workflow not in ('policy_open', 'policy_moderated',
'policy_closed'):
+ self.errors['workflow_policy'] = _(u'list_invalid_workflow_error',
u'The mailing list security must be set to open, moderated, or closed.')
+
+ archive = None
+ try:
+ archive = int(self.request.form.get('archival_policy'))
+ except TypeError:
+ pass
+
+ if archive not in (0, 1, 2):
+ self.errors['archive'] = _(u'list_invalid_archive_error', u'The
mailing list archival method must be set to all, text-only, or none.')
+
+ mailto = None
+ if creation:
+ mailto = self.request.form.get('mailto')
+ if not mailto:
+ self.errors['mailto'] = _(u'list_missing_prefix_error', u'The
mailing list must have a list prefix.')
+ elif not isValidPrefix(mailto):
+ self.errors['mailto'] = _(u'list_invalid_prefix_error', u'Only
the following characters are allowed in list address prefixes: alpha-numerics,
underscores, hyphens, and periods (i.e. A-Z, a-z, 0-9, and _-. symbols)')
+ else:
+ mailto = putils.normalizeString(mailto)
+ if hasattr(self.context, mailto):
+ self.errors['mailto'] = _(u'list_create_duplicate_error',
u'The requested list prefix is already taken.')
+
+ # If we don't pass sanity checks by this point, abort and let the user
correct their errors.
+ if self.errors and not justValidate:
+ self.add_status_message(_(u'psm_correct_errors_below', u'Please
correct the errors indicated below.'))
+ return False
+ return title, workflow, archive, mailto, managers
+
+
+ @action('validate')
+ def validate(self, target=None, fields=None):
+ putils = getToolByName(self.context, 'plone_utils')
+ result = self.validate_form(justValidate=True)
+
+ errors = dict (("oc-%s-error" % k, oc_json_error('')) for k in
_list_error_fields)
+ #fixme: should not be default, should be translated.
+ errors.update(dict (("oc-%s-error" % k, oc_json_error(v.default)) for
k, v in self.errors.items()))
+
+ mailto = self.request.form.get('mailto')
+ mailto = putils.normalizeString(mailto)
+
+ return errors
+
+
+ @action('validate-members')
+ def validate_members(self, target=None, fields=None):
+ # Check the list of members
+ form_members = self.request.form.get('members','')
+
+ members = []
+ bad_members = []
+ if not form_members:
+ return {
+ 'rejects':'',
+ 'valid':''
+ }
+ else:
+ for manager in form_members.split(','):
+ manager = manager.strip()
+ if not self.is_member(manager):
+ bad_members.append(manager)
+ else:
+ members.append(manager)
+ return {
+ 'rejects':bad_members,
+ 'valid':members
+ }
+
+class ListAddView(ListenEditBaseView):
+
+ template = ZopeTwoPageTemplateFile('create.pt')
+
+ @action('add')
+ def handle_request(self, target=None, fields=None):
+ # Get the tool used to normalize strings
+ putils = getToolByName(self.context, 'plone_utils')
+
+ result = self.validate_form(creation=True)
+ if not result:
+ return
+
+ title, workflow, archive, mailto, managers = result
+
+ # Try to create a mailing list using the mailto address to see if it's
going to be valid
+ lists_folder = self.context
+ try:
+ lists_folder.invokeFactory(OpenMailingList.portal_type, mailto)
+ except BadRequest:
+ self.errors['mailto'] = _(u'list_create_duplicate_error', u'The
requested list prefix is already taken.')
+ self.add_status_message(_(u'psm_correct_errors_below', u'Please
correct the errors indicated below.'))
+ return
+
+ list = lists_folder._getOb(mailto)
+
+ list.managers = tuple(managers)
+ list.setDescription(unicode(self.request.form.get('description','')))
+
+ old_workflow_type = list.list_type
+ new_workflow_type = _workflow_to_ml_type[workflow]
+
+ notify(ListTypeChanged(list,
+ old_workflow_type.list_marker,
+ new_workflow_type.list_marker))
+
+ list.archived = archive
+
+ self.template = None
+
+ #subscribe user to list
+ sub_list = IWriteMembershipList(list)
+ current_user = unicode(self.loggedinmember.getId())
+ sub_list.subscribe(current_user)
+
+ s_message_mapping = {'title': title}
+ s_message = _(u'list_created',
+ u'"${title}" has been created.',
+ mapping=s_message_mapping)
+
+ self.add_status_message(s_message)
+
+ self.redirect(list.absolute_url())
+
+
+class ListEditView(ListenEditBaseView):
+ template = ZopeTwoPageTemplateFile('edit.pt')
+
+ @action('edit')
+ def handle_request(self, target=None, fields=None):
+ result = self.validate_form()
+
+ if not result:
+ return
+
+ title, workflow, archive, mailto, managers = result
+
+ list = self.context
+
+ list.setTitle(title)
+ list.setDescription(unicode(self.request.form.get('description','')))
+
+ old_workflow_type = list.list_type
+ new_workflow_type = _workflow_to_ml_type[workflow]
+
+ notify(ListTypeChanged(list,
+ old_workflow_type.list_marker,
+ new_workflow_type.list_marker))
+
+ list.archived = archive
+
+ list.managers = tuple(managers)
+
+ self.template = None
+
+ s_message = _(u'list_preferences_updated',
+ u'Your changes have been saved.')
+
+ self.add_status_message(s_message)
+
+ self.redirect(list.absolute_url())
+
+ def workflow_policy(self):
+ return _ml_type_to_workflow[self.context.list_type]
+
+ def mailto(self):
+ return self.context.mailto.split("@")[0]
+
+
+
# uh.. if you are going write meta factories you should write tests too
# isn't this what super and mixins are is suppose to solve?
def make_nui_listen_view_class(ListenClass, set_errors=False,
add_update=False):
@@ -98,10 +358,77 @@
return NuiListenView
+
+class ModerationView(BaseModerationView):
+ """A view for moderating things """
+
+ def __call__(self):
+ #figure out request method
+ method = self.request.environ['REQUEST_METHOD']
+ if method == "GET":
+ return self.index()
+
+ d = self.request.form
+ self.errors = ''
+ post = email = None
+ action = ''
+ postid = None
+ reject_reason = ''
+
+ # first check if mass moderating all posts
+ if d.get('discard_all_posts', False):
+ action = 'discard'
+ policy = getAdapter(self.context, IEmailPostPolicy)
+ for post in self.get_pending_lists():
+ postid = post['postid']
+ email = post['user']
+ req = dict(action=action, email=email, postid=postid)
+ policy_result = policy.enforce(req)
+ if policy_result == MODERATION_FAILED:
+ self.errors = _(u'Could not moderate!')
+ break
+ return self.index()
+
+ for name, value in d.items():
+ if name.endswith('_approve') or \
+ name.endswith('_discard') or \
+ name.endswith('_reject'):
+ action = value.split('_')[-1]
+ elif name == 'postid':
+ postid = int(value)
+ elif name == 'email':
+ email = value
+ elif name == 'reject_reason':
+ reject_reason = value
+
+ json = {}
+ # having a post id specified means that we need to moderate posts
+ if postid is not None:
+ # using email post policy
+ # may have to try enforcing the ITTWPostPolicy as well on failure
+ policy = getAdapter(self.context, IEmailPostPolicy)
+ req = dict(action=action, email=email, postid=postid,
reject_reason=reject_reason)
+ policy_result = policy.enforce(req)
+ if policy_result == MODERATION_FAILED:
+ self.errors = _(u'Could not moderate!')
+ json = {'post_%s' % postid : {'action': 'delete'}}
+ else:
+ # same idea between membership policy
+ # may have to try the IUserTTWMembershipPolicy if the email policy
fails
+ policy = getAdapter(self.context, IUserEmailMembershipPolicy)
+ req = dict(action=action, email=email, reject_reason=reject_reason)
+ policy_result = policy.enforce(req)
+ if policy_result == MODERATION_FAILED:
+ self.errors = _(u'Could not moderate!')
+ json = {'member_%s' % postid : {'action': 'delete'}}
+ if 'mode' in self.request.keys() and self.request.mode == 'async':
+ return json
+ else:
+ self.redirect(self.request.ACTUAL_URL)
+
+
# prefixing everything is unnecessary
NuiMailingListView = make_nui_listen_view_class(MailingListView)
-NuiMailingListAddView = make_nui_listen_view_class(MailingListAddForm,
set_errors=True, add_update=True)
-NuiMailingListEditView = make_nui_listen_view_class(MailingListEditForm,
set_errors=True, add_update=True)
from Products.listen.interfaces import IMembershipPendingList
Modified: opencore/trunk/opencore/listen/utils.py
===================================================================
--- opencore/trunk/opencore/listen/utils.py 2008-01-02 17:02:19 UTC (rev
12226)
+++ opencore/trunk/opencore/listen/utils.py 2008-01-02 17:03:48 UTC (rev
12227)
@@ -5,30 +5,29 @@
from zope.app.component.hooks import getSite
from Products.CMFCore.utils import getToolByName
-from Products.listen.interfaces.mailinglist import check_mailto
+from Products.listen.interfaces.mailinglist import check_mailto,
ManagerMailTo, InvalidMailTo
_ = MessageFactory("opencore")
-regex = re.compile(r'[^A-Za-z0-9_\-\.+]')
+invalid_list_prefix_re = re.compile(r'[^\w.-]')
-class InvalidPrefix(ValidationError):
- __doc__ = _(u'listen_prefix_validation_error', u"Only the following
characters are allowed in "
- "list address prefixes: alpha-numerics, underscores, "
- "hyphens, periods, and plus signs (i.e. A-Z, a-z, 0-9, "
- "and _-.+ symbols).")
-
+#XXX: I'm not sure how this is supposed to work. - novalis
def isValidPrefix(prefix):
"""
Returns True if the prefix only contains valid email prefix chars,
- raises an InvalidPrefix exception otherwise.
+ returns False otherwise
"""
- # use getSite since we've got no other acq hook
suffix = getSuffix()
- check_mailto(prefix + suffix)
+ try:
+ check_mailto(prefix + suffix)
+ except InvalidMailTo:
+ return False
+ except ManagerMailTo:
+ return False
- match = regex.search(prefix)
+ match = invalid_list_prefix_re.search(prefix)
if match is not None:
- raise InvalidPrefix
+ return False
return True
def getSuffix():
@@ -37,7 +36,7 @@
the opencore_properties PropertySheet. Requires a context object
from inside the site so the properties tool can be retrieved.
"""
- # use threadlocal site to hook into acquisition context
+ # use getSite since we've got no other acq hook
site = getSite()
ptool = getToolByName(site, 'portal_properties')
ocprops = ptool._getOb('opencore_properties')
--
Archive:
http://www.openplans.org/projects/opencore/lists/openplans-svn/archive/2008/01/1199293429365
To unsubscribe send an email with subject unsubscribe to [EMAIL PROTECTED]
Please contact [EMAIL PROTECTED] for questions.