diff --git a/castle/cms/browser/content/actions.py b/castle/cms/browser/content/actions.py
index 7860eb39c..16c723fe2 100644
--- a/castle/cms/browser/content/actions.py
+++ b/castle/cms/browser/content/actions.py
@@ -11,7 +11,10 @@
from castle.cms.utils import get_paste_data
from castle.cms.utils import is_max_paste_items
from castle.cms.utils import get_template_repository_info
+from castle.cms.tasks.email import send_email_reminder
from plone.app.content.browser import actions
+from plone.app.workflow.browser import sharing
+from plone.app.workflow.events import LocalrolesModifiedEvent
from plone.memoize.view import memoize
from plone.uuid.interfaces import IUUID
from Products.CMFCore.utils import getToolByName
@@ -19,6 +22,7 @@
from Products.CMFPlone.utils import safe_unicode
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from Products.statusmessages.interfaces import IStatusMessage
+
from z3c.form import button
from z3c.form import field
from z3c.form import form
@@ -26,7 +30,10 @@
from zope.interface import Interface
import plone.api as api
+import sys
import zope.schema as schema
+from zope.event import notify
+from zExceptions import Forbidden
class ObjectPasteView(actions.ObjectPasteView):
@@ -326,3 +333,118 @@ def do_redirect(self, context):
utils = Utils(self.context, self.request)
target = utils.get_object_url(context)
return self.request.response.redirect(target)
+
+
+class SharingView(sharing.SharingView):
+
+ def __call__(self):
+
+ postback = self.handle_form()
+ if postback:
+ # Reset python recursion limit to baseline if previously raised
+ sys.setrecursionlimit(1000)
+ return self.template()
+ else:
+ context_state = self.context.restrictedTraverse(
+ "@@plone_context_state")
+ url = context_state.view_url()
+ self.request.response.redirect(url)
+
+ def large_count(self):
+ """
+ Displays an alert indicating that we're operating in a view
+ with a high document count, which slows down the process of
+ granting permissions.
+ """
+
+ try:
+ event = LocalrolesModifiedEvent(self.context, self.request)
+ obj = event.object
+ if obj._count:
+ return True if obj._count.value > 1000 else False
+ except AttributeError:
+ return False
+
+ def handle_form(self):
+ """
+ Overrides the 'handle_form' method so we can temporarily increase
+ the python recursion limit if needed.
+
+ Also sends a notification email to individual users assigned to a page.
+ """
+ postback = True
+
+ form = self.request.form
+ submitted = form.get('form.submitted', False)
+ save_button = form.get('form.button.Save', None) is not None
+ cancel_button = form.get('form.button.Cancel', None) is not None
+ message = form.get('message_optional', None)
+ if submitted and save_button and not cancel_button:
+ if not self.request.get('REQUEST_METHOD', 'GET') == 'POST':
+ raise Forbidden
+
+ authenticator = self.context.restrictedTraverse('@@authenticator',
+ None)
+ if not authenticator.verify():
+ raise Forbidden
+
+ event = LocalrolesModifiedEvent(self.context, self.request)
+ obj = event.object
+ try:
+ if obj._count.value > 1000:
+ # XXX: We're increasing the recursion limit here to prevent the
+ # 'maximum recursion depth exceeded' error thrown by 'cPickle'
+ # that occurs when granting permissions in a large directory
+ # (i.e. the 'image-directory')
+ sys.setrecursionlimit(2000)
+ except AttributeError:
+ # User search form submitted, no need to get count
+ pass
+
+ # Update the acquire-roles setting
+ if self.can_edit_inherit():
+ inherit = bool(form.get('inherit', False))
+ reindex = self.update_inherit(inherit, reindex=False)
+ else:
+ reindex = False
+
+ # Update settings for users and groups
+ entries = form.get('entries', [])
+ roles = [r['id'] for r in self.roles()]
+
+ settings = []
+ for entry in entries:
+ assigned_roles=[r for r in roles if entry.get('role_%s' % r, False)]
+ settings.append(
+ dict(id=entry['id'],
+ type=entry['type'],
+ roles=assigned_roles))
+
+ if entry['type'] == 'user' and 'Reviewer' in assigned_roles:
+ # Only send emails to individual users assigned as 'Reviewers'
+ user = api.user.get(entry['id'])
+ email = user.getProperty('email')
+ if email:
+ data = dict(
+ uid=user.getId(),
+ name=user.getProperty('fullname') or user.getId(),
+ email=email,
+ roles=roles,
+ message=message
+ )
+ send_email_reminder.delay(obj, data)
+
+ if settings:
+ reindex = self.update_role_settings(settings, reindex=False) \
+ or reindex
+ if reindex:
+ self.context.reindexObjectSecurity()
+ notify(LocalrolesModifiedEvent(self.context, self.request))
+ IStatusMessage(self.request).addStatusMessage(
+ _(u"Changes saved."), type='info')
+
+ # Other buttons return to the sharing page
+ if cancel_button:
+ postback = False
+
+ return postback
diff --git a/castle/cms/browser/content/configure.zcml b/castle/cms/browser/content/configure.zcml
index 9f54346ef..53ac53e79 100644
--- a/castle/cms/browser/content/configure.zcml
+++ b/castle/cms/browser/content/configure.zcml
@@ -228,6 +228,14 @@
template="templates/parallaxview.pt"
/>
+
You have been assigned a new page:
+%s
+When your task is complete, you may un-assign yourself from this page.
""" % ( + item['name'], obj_path) + message = item.get('message') + if message: + html += """ +%s
+ """ % (message) + + utils.send_email( + recipients=recipients, + subject=subject, + html=html + ) + except Exception: + logger.warn('Could not send assignment email ', exc_info=True) + else: + new_cache = deepcopy(reminder_cache) # Not sure if deepcopy is necessary + new_cache.pop(key, None) + cache.set(cache_key, new_cache) + + +def run(app): + singleton.SingleInstance('autopublish') + + user = app.acl_users.getUser('admin') # noqa + newSecurityManager(None, user.__of__(app.acl_users)) # noqa + + for oid in app.objectIds(): # noqa + obj = app[oid] # noqa + if IPloneSiteRoot.providedBy(obj): + try: + send_reminders(obj) + except Exception: + logger.error('Could not update content for %s' % oid, exc_info=True) + + +if __name__ == '__main__': + run(app) # noqa \ No newline at end of file diff --git a/castle/cms/exportimport/install.py b/castle/cms/exportimport/install.py index a7b9149a7..3dad0678d 100644 --- a/castle/cms/exportimport/install.py +++ b/castle/cms/exportimport/install.py @@ -14,7 +14,9 @@ "hasImage": "BooleanIndex", "trashed": "BooleanIndex", "has_private_parents": "BooleanIndex", - "self_or_child_has_title_description_and_image": "BooleanIndex" + "self_or_child_has_title_description_and_image": "BooleanIndex", + "actors": "KeywordIndex", + "assigned_users": "KeywordIndex" } REMOVE_INDEXES = [ @@ -34,7 +36,9 @@ 'image_info', 'navigation_label', 'has_private_parents', - 'self_or_child_has_title_description_and_image' + 'self_or_child_has_title_description_and_image', + 'actors', + 'assigned_users' ] REMOVE_METADATA = [ diff --git a/castle/cms/fragments/templates/dashboard-statistics.pt b/castle/cms/fragments/templates/dashboard-statistics.pt index 82012c7ee..7536ece56 100644 --- a/castle/cms/fragments/templates/dashboard-statistics.pt +++ b/castle/cms/fragments/templates/dashboard-statistics.pt @@ -1,21 +1,176 @@| Title | +Last modified by | +Modified | +
|---|---|---|
| + ${item/Title} + | +${creator/name_or_id} |
+
+
+
+ |
+
| Title | +Created by | +Created | +
|---|---|---|
| + ${item/Title} + | +${creator/name_or_id} |
+
+
+
+ |
+
| Title | +Created by | +Created | +
|---|---|---|
| + ${item/Title} + | +${creator/name_or_id} |
+
+
+
+ |
+
| Title | -Last modified by | -Modified | +Title | +Last modified by | +Modified |
|---|
| Title | -Created by | -Created | +Title | +Created by | +Created |
|---|
| Title | -Last modified by | -Modified | +Title | +Last modified by | +Modified |
|---|