# HG changeset patch # User Thierry Florac # Date 1510311925 -3600 # Node ID c8e9ace0bf356a22826c8c171f9c71b8d98e7a40 # Parent d88bd7cdaebfa27f8f0e8dda4476f1da58c85f92 Moved 'review' code to 'features' module diff -r d88bd7cdaebf -r c8e9ace0bf35 src/pyams_content/shared/common/review.py --- a/src/pyams_content/shared/common/review.py Fri Nov 10 12:04:50 2017 +0100 +++ b/src/pyams_content/shared/common/review.py Fri Nov 10 12:05:25 2017 +0100 @@ -14,220 +14,10 @@ # import standard library -import os -from datetime import datetime -from uuid import uuid4 # import interfaces -from pyams_content.interfaces import READER_ROLE -from pyams_content.interfaces.review import IReviewManager, IReviewComment, IReviewComments, \ - REVIEW_COMMENTS_ANNOTATION_KEY, CommentAddedEvent, ICommentAddedEvent -from pyams_content.shared.common.interfaces import IWfSharedContent, IWfSharedContentRoles -from pyams_i18n.interfaces import II18n -from pyams_mail.interfaces import IPrincipalMailInfo -from pyams_security.interfaces import ISecurityManager, IProtectedObject -from pyams_security.interfaces.notification import INotificationSettings -from pyramid_chameleon.interfaces import IChameleonTranslate -from pyramid_mailer.interfaces import IMailer -from zope.annotation.interfaces import IAnnotations -from zope.location.interfaces import ISublocations -from zope.traversing.interfaces import ITraversable # import packages -from persistent import Persistent -from pyams_mail.message import HTMLMessage -from pyams_security.principal import MissingPrincipal -from pyams_utils.adapter import adapter_config, ContextAdapter -from pyams_utils.container import BTreeOrderedContainer -from pyams_utils.registry import query_utility -from pyams_utils.request import check_request, query_request -from pyams_utils.url import absolute_url -from pyramid.events import subscriber -from pyramid.threadlocal import get_current_registry -from pyramid_chameleon.zpt import PageTemplateFile -from zope.container.contained import Contained -from zope.interface import implementer -from zope.location import locate -from zope.schema.fieldproperty import FieldProperty -from pyams_content import _ - - -@implementer(IReviewComment) -class ReviewComment(Persistent, Contained): - """Review comment persistent class""" - - owner = FieldProperty(IReviewComment['owner']) - comment = FieldProperty(IReviewComment['comment']) - comment_type = FieldProperty(IReviewComment['comment_type']) - creation_date = FieldProperty(IReviewComment['creation_date']) - - def __init__(self, owner, comment, comment_type='comment'): - self.owner = owner - self.comment = comment - self.comment_type = comment_type - self.creation_date = datetime.utcnow() - - -@implementer(IReviewComments) -class ReviewCommentsContainer(BTreeOrderedContainer): - """Review comments container""" - - reviewers = FieldProperty(IReviewComments['reviewers']) - - def clear(self): - for k in self.keys()[:]: - del self[k] - - def add_comment(self, comment): - uuid = str(uuid4()) - self[uuid] = comment - reviewers = self.reviewers or set() - reviewers.add(comment.owner) - self.reviewers = reviewers - get_current_registry().notify(CommentAddedEvent(self.__parent__, comment)) - - -@adapter_config(context=IWfSharedContent, provides=IReviewComments) -def SharedContentReviewCommentsFactory(context): - """Shared content review comments factory""" - annotations = IAnnotations(context) - comments = annotations.get(REVIEW_COMMENTS_ANNOTATION_KEY) - if comments is None: - comments = annotations[REVIEW_COMMENTS_ANNOTATION_KEY] = ReviewCommentsContainer() - locate(comments, context, '++review-comments++') - return comments - - -@adapter_config(name='review-comments', context=IWfSharedContent, provides=ITraversable) -class SharedContentReviewCommentsNamespace(ContextAdapter): - """++review-comments++ namespace traverser""" - - def traverse(self, name, furtherpath=None): - return IReviewComments(self.context) - - -@adapter_config(name='review-comments', context=IWfSharedContent, provides=ISublocations) -class SharedContentReviewCommentsSublocations(ContextAdapter): - """Shared content review comments sub-location adapter""" - - def sublocations(self): - return IReviewComments(self.context).values() - - -@adapter_config(context=IWfSharedContent, provides=IReviewManager) -class SharedContentReviewAdapter(ContextAdapter): - """Shared content review adapter""" - - review_template = PageTemplateFile(os.path.join(os.path.dirname(__file__), - 'zmi/templates/review-notification.pt')) - - def ask_review(self, reviewers, comment, notify_all=True): - """Ask for content review""" - roles = IWfSharedContentRoles(self.context, None) - if roles is None: - return - # check request - request = check_request() - translate = request.localizer.translate - # initialize mailer - security = query_utility(ISecurityManager) - settings = INotificationSettings(security) - sender_name = request.principal.title if request.principal is not None else settings.sender_name - sender_address = settings.sender_email - sender = security.get_principal(request.principal.id, info=False) - sender_mail_info = IPrincipalMailInfo(sender, None) - if sender_mail_info is not None: - for sender_name, sender_address in sender_mail_info.get_addresses(): - break - if settings.enable_notifications: - mailer = query_utility(IMailer, name=settings.mailer) - else: - mailer = None - # create message - message_body = self.review_template(request=request, - context=self.context, - translate=query_utility(IChameleonTranslate), - options={'settings': settings, - 'comment': comment, - 'sender': sender_name}) - # notify reviewers - notifications = 0 - readers = roles.readers - for reviewer in reviewers: - if settings.enable_notifications and \ - (mailer is not None) and \ - (notify_all or (reviewer not in readers)): - principal = security.get_principal(reviewer, info=False) - if not isinstance(principal, MissingPrincipal): - mail_info = IPrincipalMailInfo(principal, None) - if mail_info is not None: - for name, address in mail_info.get_addresses(): - message = HTMLMessage( - subject=translate(_("[{service_name}] A content review is requested")).format( - service_name=settings.subject_prefix), - fromaddr='{name} <{address}>'.format(name=sender_name, - address=sender_address), - toaddr='{name} <{address}>'.format(name=name, address=address), - html=message_body) - mailer.send(message) - notifications += 1 - readers.add(reviewer) - roles.readers = readers - # add comment - review_comment = ReviewComment(owner=request.principal.id, - comment=comment, - comment_type='request') - IReviewComments(self.context).add_comment(review_comment) - # return notifications count - return notifications - - -# -# Review comment notification -# - -try: - from pyams_notify.interfaces import INotification, INotificationHandler - from pyams_notify.event import Notification -except ImportError: - pass -else: - - @subscriber(ICommentAddedEvent) - def handle_new_comment(event): - """Handle new review comment""" - request = query_request() - if request is None: - return - content = event.object - translate = request.localizer.translate - notification = Notification(request=request, - context=content, - source=event.comment.owner, - action='notify', - category='content.review', - message=translate(_("A new comment was added on content « {0} »")).format( - II18n(content).query_attribute('title', request=request)), - url=absolute_url(content, request, 'admin#review-comments.html'), - comments=IReviewComments(content)) - notification.send() - - - @adapter_config(name='content.review', context=INotification, provides=INotificationHandler) - class ContentReviewNotificationHandler(ContextAdapter): - """Content review notification handler""" - - def get_target(self): - context = self.context.context - principals = set() - protection = IProtectedObject(context, None) - if protection is not None: - principals |= protection.get_principals(READER_ROLE) - comments = self.context.user_data.get('comments') - if comments is not None: - principals |= comments.reviewers - source_id = self.context.source['id'] - if source_id in principals: - principals.remove(source_id) - return {'principals': tuple(principals)} +# imports for backward compatibility: module moved to pyams_content.features.review !! +from pyams_content.features.review import ReviewComment, ReviewCommentsContainer