def notify_reset_password(self, user: User, reset_password_token: str) -> None: """ Reset password link for user :param user: user to notify :param reset_password_token: token for resetting password """ logger.debug(self, "user: {}".format(user.user_id)) logger.info(self, "Generating reset password email to {}".format(user.email)) translator = Translator(self.config, default_lang=user.lang) email_sender = EmailSender(self.config, self._smtp_config, self.config.EMAIL__NOTIFICATION__ACTIVATED) translated_subject = translator.get_translation( self.config.EMAIL__NOTIFICATION__RESET_PASSWORD_REQUEST__SUBJECT) subject = translated_subject.replace(EST.WEBSITE_TITLE, str(self.config.WEBSITE__TITLE)) from_header = self._get_sender() to_header = EmailAddress(user.get_display_name(), user.email) html_template_file_path = ( self.config. EMAIL__NOTIFICATION__RESET_PASSWORD_REQUEST__TEMPLATE__HTML) # TODO - G.M - 2018-08-17 - Generate token context = { "user": user, "logo_url": get_email_logo_frontend_url(self.config), "reset_password_url": get_reset_password_frontend_url(self.config, token=reset_password_token, email=user.email), } body_html = self._render_template( mako_template_filepath=html_template_file_path, context=context, translator=translator) message = EmailNotificationMessage( subject=subject, from_header=from_header, to_header=to_header, body_html=body_html, lang=translator.default_lang, ) send_email_through(config=self.config, sendmail_callable=email_sender.send_mail, message=message)
def notify_created_account( self, user: User, password: str, origin_user: typing.Optional[User] = None) -> None: """ Send created account email to given user. :param password: choosed password :param user: user to notify """ logger.info(self, "Generating created account mail to {}".format(user.email)) email_sender = EmailSender(self.config, self._smtp_config, self.config.EMAIL__NOTIFICATION__ACTIVATED) translator = Translator(self.config, default_lang=user.lang) translated_subject = translator.get_translation( self.config.EMAIL__NOTIFICATION__CREATED_ACCOUNT__SUBJECT) subject = translated_subject.replace(EST.WEBSITE_TITLE, str(self.config.WEBSITE__TITLE)) from_header = self._get_sender(origin_user) to_header = EmailAddress(user.get_display_name(), user.email) html_template_file_path = self.config.EMAIL__NOTIFICATION__CREATED_ACCOUNT__TEMPLATE__HTML context = { "origin_user": origin_user, "user": user, "password": password, "logo_url": get_email_logo_frontend_url(self.config), "login_url": get_login_frontend_url(self.config), } translator = Translator(self.config, default_lang=user.lang) body_html = self._render_template( mako_template_filepath=html_template_file_path, context=context, translator=translator) message = EmailNotificationMessage( subject=subject, from_header=from_header, to_header=to_header, body_html=body_html, lang=translator.default_lang, ) send_email_through(config=self.config, sendmail_callable=email_sender.send_mail, message=message)
def __init__( self, session: TracimSession, current_user: typing.Optional[User], config: CFG, force_role: bool = False, show_deleted: bool = False, access_types_filter: typing.Optional[ typing.List[WorkspaceAccessType]] = None, ): """ :param current_user: Current user of context :param force_role: If True, app role in queries even if admin """ session.assert_event_mechanism() self._session = session self._user = current_user self._config = config self._force_role = force_role self.show_deleted = show_deleted self._access_types_filter = access_types_filter default_lang = None if self._user: default_lang = self._user.lang self.translator = Translator(app_config=self._config, default_lang=default_lang)
def notify_reset_password(self, user: User, reset_password_token: str) -> None: """ Reset password link for user :param user: user to notify :param reset_password_token: token for resetting password """ logger.debug(self, "user: {}".format(user.user_id)) logger.info(self, "Generating reset password email to {}".format(user.email)) translator = Translator(self.config, default_lang=user.lang) email_sender = EmailSender( self.config, self._smtp_config, self.config.EMAIL__NOTIFICATION__ACTIVATED ) translated_subject = translator.get_translation( self.config.EMAIL__NOTIFICATION__RESET_PASSWORD_REQUEST__SUBJECT ) subject = translated_subject.replace(EST.WEBSITE_TITLE, str(self.config.WEBSITE__TITLE)) message = MIMEMultipart("alternative") message["Subject"] = subject message["From"] = self._get_sender() message["To"] = formataddr((user.get_display_name(), user.email)) html_template_file_path = ( self.config.EMAIL__NOTIFICATION__RESET_PASSWORD_REQUEST__TEMPLATE__HTML ) # TODO - G.M - 2018-08-17 - Generate token context = { "user": user, "logo_url": get_email_logo_frontend_url(self.config), "reset_password_url": get_reset_password_frontend_url( self.config, token=reset_password_token, email=user.email ), } body_html = self._render_template( mako_template_filepath=html_template_file_path, context=context, translator=translator ) part2 = MIMEText(body_html, "html", "utf-8") # Attach parts into message container. # According to RFC 2046, the last part of a multipart message, # in this case the HTML message, is best and preferred. message.attach(part2) send_email_through( config=self.config, sendmail_callable=email_sender.send_mail, message=message )
def notify_created_account(self, user: User, password: str) -> None: """ Send created account email to given user. :param password: choosed password :param user: user to notify """ logger.info(self, "Generating created account mail to {}".format(user.email)) email_sender = EmailSender( self.config, self._smtp_config, self.config.EMAIL__NOTIFICATION__ACTIVATED ) translator = Translator(self.config, default_lang=user.lang) translated_subject = translator.get_translation( self.config.EMAIL__NOTIFICATION__CREATED_ACCOUNT__SUBJECT ) subject = translated_subject.replace(EST.WEBSITE_TITLE, str(self.config.WEBSITE__TITLE)) message = MIMEMultipart("alternative") message["Subject"] = subject message["From"] = self._get_sender() message["To"] = formataddr((user.get_display_name(), user.email)) html_template_file_path = self.config.EMAIL__NOTIFICATION__CREATED_ACCOUNT__TEMPLATE__HTML context = { "user": user, "password": password, "logo_url": get_email_logo_frontend_url(self.config), "login_url": get_login_frontend_url(self.config), } translator = Translator(self.config, default_lang=user.lang) body_html = self._render_template( mako_template_filepath=html_template_file_path, context=context, translator=translator ) part2 = MIMEText(body_html, "html", "utf-8") # Attach parts into message container. # According to RFC 2046, the last part of a multipart message, # in this case the HTML message, is best and preferred. message.attach(part2) send_email_through( config=self.config, sendmail_callable=email_sender.send_mail, message=message )
def notify_upload_permission( self, emitter: UserInContext, workspace_in_context: WorkspaceInContext, upload_permission_receivers: typing.List[UploadPermissionInContext], upload_permission_password: str, ) -> None: """ Send mails to notify users for sharing content :param emitter: User emitter of the sharing :param workspace_in_context: workspace where receivers can now upload file :param upload_permission_receivers: list of upload_permission :param upload_permission_password: cleartext password of the sharing """ email_sender = EmailSender(self.config, self._smtp_config, self.config.EMAIL__NOTIFICATION__ACTIVATED) upload_permission_password_enabled = False if upload_permission_password: upload_permission_password_enabled = True translator = Translator(self.config, default_lang=emitter.lang) message = self._notify_emitter( emitter=emitter, workspace_in_context=workspace_in_context, upload_permission_receivers=upload_permission_receivers, upload_permission_password=upload_permission_password, translator=translator, ) send_email_through(config=self.config, sendmail_callable=email_sender.send_mail, message=message) emails_receivers_list = [ upload_permission.email for upload_permission in upload_permission_receivers ] logger.info( self, 'Generating upload permission mail from user "{}" to "{}"'.format( emitter.user_id, "".join(emails_receivers_list)), ) for upload_permission in upload_permission_receivers: message = self._notify_receiver( emitter=emitter, workspace=workspace_in_context, upload_permission=upload_permission, upload_permission_password_enabled= upload_permission_password_enabled, translator=translator, ) send_email_through(config=self.config, sendmail_callable=email_sender.send_mail, message=message)
def notify__share__content( self, emitter: User, shared_content: ContentInContext, content_share_receivers: typing.List[ContentShareInContext], share_password: str, ) -> None: """ Send mails to notify users for sharing content :param emitter: User emitter of the sharing :param shared_content: content that is now shared :param content_share_receivers: list of content share :param share_password: cleartext password of the sharing """ email_sender = EmailSender(self.config, self._smtp_config, self.config.EMAIL__NOTIFICATION__ACTIVATED) share_password_enabled = False if share_password: share_password_enabled = True translator = Translator(self.config, default_lang=emitter.lang) message = self._notify_emitter( emitter=emitter, shared_content=shared_content, content_share_receivers=content_share_receivers, share_password=share_password, translator=translator, ) send_email_through(config=self.config, sendmail_callable=email_sender.send_mail, message=message) for content_share in content_share_receivers: emails_receivers_list = [ share_content.email for share_content in content_share_receivers ] logger.info( self, 'Generating share mail from user "{}" to "{}"'.format( emitter.user_id, "".join(emails_receivers_list)), ) message = self._notify_receiver( emitter=emitter, shared_content=shared_content, content_share=content_share, share_password_enabled=share_password_enabled, translator=translator, ) send_email_through(config=self.config, sendmail_callable=email_sender.send_mail, message=message)
def _notify_receiver( self, emitter: User, workspace: Workspace, upload_permission: UploadPermissionInContext, upload_permission_password_enabled: bool, translator: Translator, ) -> Message: logger.info( self, 'preparing email from user "{}" for the upload permission on workspace "{}" to "{}"' .format(emitter.user_id, workspace.workspace_id, upload_permission.email), ) translated_subject = translator.get_translation( self.config. EMAIL__NOTIFICATION__UPLOAD_PERMISSION_TO_RECEIVER__SUBJECT) subject = translated_subject.format( website_title=self.config.WEBSITE__TITLE, emitter_name=emitter.display_name) from_header = self._get_sender(emitter) to_header = EmailAddress.from_rfc_email_address( upload_permission.email) html_template_file_path = ( self.config. EMAIL__NOTIFICATION__UPLOAD_PERMISSION_TO_RECEIVER__TEMPLATE__HTML) receiver = EmailUser(user_email=upload_permission.email) context = { "emitter": emitter, "workspace": workspace, "upload_permission": upload_permission, "receiver": receiver, "upload_permission_password_enabled": upload_permission_password_enabled, } body_html = self._render_template( mako_template_filepath=html_template_file_path, context=context, translator=translator) message = EmailNotificationMessage( subject=subject, from_header=from_header, to_header=to_header, body_html=body_html, lang=translator.default_lang, ) return message
def _notify_receiver( self, emitter: User, shared_content: ContentInContext, content_share: ContentShareInContext, share_password_enabled: bool, translator: Translator, ) -> Message: logger.info( self, 'preparing email from user "{}" for the share on content "{}" to "{}"' .format(emitter.user_id, shared_content.content_id, content_share.email), ) translated_subject = translator.get_translation( self.config.EMAIL__NOTIFICATION__SHARE_CONTENT_TO_RECEIVER__SUBJECT ) subject = translated_subject.format( website_title=self.config.WEBSITE__TITLE, content_filename=shared_content.filename, emitter_name=emitter.display_name, ) from_header = self._get_sender(emitter) to_header = EmailAddress.from_rfc_email_address(content_share.email) username, address = email.utils.parseaddr(content_share.email) html_template_file_path = ( self.config. EMAIL__NOTIFICATION__SHARE_CONTENT_TO_RECEIVER__TEMPLATE__HTML) receiver = EmailUser(user_email=content_share.email) context = { "emitter": emitter, "shared_content": shared_content, "content_share": content_share, "share_password_enabled": share_password_enabled, "receiver": receiver, } body_html = self._render_template( mako_template_filepath=html_template_file_path, context=context, translator=translator) message = EmailNotificationMessage( subject=subject, from_header=from_header, to_header=to_header, body_html=body_html, lang=translator.default_lang, ) return message
def _notify_emitter( self, emitter: UserInContext, workspace_in_context: WorkspaceInContext, upload_permission_receivers: typing.List[UploadPermissionInContext], upload_permission_password: str, translator: Translator, ) -> Message: logger.info( self, 'preparing email to user "{}" about upload_permission on workspace "{}" info created' .format(emitter.user_id, workspace_in_context.workspace_id), ) translated_subject = translator.get_translation( self.config. EMAIL__NOTIFICATION__UPLOAD_PERMISSION_TO_EMITTER__SUBJECT) subject = translated_subject.format( website_title=self.config.WEBSITE__TITLE, nb_receivers=len(upload_permission_receivers), workspace_name=workspace_in_context.label, ) from_header = self._get_sender() to_header = EmailAddress(emitter.display_name, emitter.email) html_template_file_path = ( self.config. EMAIL__NOTIFICATION__UPLOAD_PERMISSION_TO_EMITTER__TEMPLATE__HTML) context = { "emitter": emitter, "workspace": workspace_in_context, "upload_permission_receivers": upload_permission_receivers, "upload_permission_password": upload_permission_password, } body_html = self._render_template( mako_template_filepath=html_template_file_path, context=context, translator=translator) message = EmailNotificationMessage( subject=subject, from_header=from_header, to_header=to_header, body_html=body_html, lang=translator.default_lang, ) return message
def _notify_emitter( self, emitter: User, shared_content: ContentInContext, content_share_receivers: typing.List[ContentShareInContext], share_password: str, translator: Translator, ) -> Message: logger.info( self, 'preparing email to user "{}" about share on content "{}" info created'.format( emitter.user_id, shared_content.content_id ), ) translated_subject = translator.get_translation( self.config.EMAIL__NOTIFICATION__SHARE_CONTENT_TO_EMITTER__SUBJECT ) subject = translated_subject.format( website_title=self.config.WEBSITE__TITLE, content_filename=shared_content.filename, nb_receivers=len(content_share_receivers), ) from_header = self._get_sender() to_header = EmailAddress(emitter.display_name, emitter.email) html_template_file_path = ( self.config.EMAIL__NOTIFICATION__SHARE_CONTENT_TO_EMITTER__TEMPLATE__HTML ) context = { "emitter": emitter, "shared_content": shared_content, "content_share_receivers": content_share_receivers, "share_password": share_password, } body_html = self._render_template( mako_template_filepath=html_template_file_path, context=context, translator=translator ) message = EmailNotificationMessage( subject=subject, from_header=from_header, to_header=to_header, body_html=body_html, lang=translator.default_lang, ) return message
def _notify_new_upload( self, workspace_in_context: WorkspaceInContext, receiver: UserInContext, uploader: EmailUser, uploader_message: typing.Optional[str], uploaded_contents: typing.List[ContentInContext], translator: Translator, ) -> Message: logger.info( self, 'preparing email to user "{}" about new upload on workspace "{}" info created' .format(receiver.user_id, workspace_in_context.workspace_id), ) translated_subject = translator.get_translation( self.config.EMAIL__NOTIFICATION__NEW_UPLOAD_EVENT__SUBJECT) subject = translated_subject.format( website_title=self.config.WEBSITE__TITLE, uploader_username=uploader.username, nb_uploaded_contents=len(uploaded_contents), workspace_name=workspace_in_context.label, ) from_header = self._get_sender() to_header = EmailAddress(receiver.display_name, receiver.email) html_template_file_path = self.config.EMAIL__NOTIFICATION__NEW_UPLOAD_EVENT__TEMPLATE__HTML context = { "receiver": receiver, "workspace": workspace_in_context, "uploader": uploader, "uploaded_contents": uploaded_contents, "uploader_message": uploader_message, } body_html = self._render_template( mako_template_filepath=html_template_file_path, context=context, translator=translator) message = EmailNotificationMessage( subject=subject, from_header=from_header, to_header=to_header, body_html=body_html, lang=translator.default_lang, ) return message
def __init__( self, session: Session, current_user: User, config: CFG, force_role: bool = False, show_deleted: bool = False, ): """ :param current_user: Current user of context :param force_role: If True, app role in queries even if admin """ self._session = session self._user = current_user self._config = config self._force_role = force_role self.show_deleted = show_deleted default_lang = None if self._user: default_lang = self._user.lang self.translator = Translator(app_config=self._config, default_lang=default_lang)
def notify_new_upload( self, uploader_username: str, uploader_email: str, uploader_message: typing.Optional[str], workspace_in_context: WorkspaceInContext, uploaded_contents: typing.List[ContentInContext], ) -> None: email_sender = EmailSender(self.config, self._smtp_config, self.config.EMAIL__NOTIFICATION__ACTIVATED) notifiable_roles = WorkspaceApi( current_user=None, session=self.session, config=self.config).get_notifiable_roles( workspace_in_context.workspace, force_notify=True) for role in notifiable_roles: logger.info( self, 'Generating new upload notification in workspace "{}" from "{}" to "{}"' .format(workspace_in_context.workspace_id, uploader_username, role.user.email), ) translator = Translator(app_config=self.config, default_lang=role.user.lang) uploader = EmailUser(username=uploader_username, user_email=uploader_email) message = self._notify_new_upload( workspace_in_context=workspace_in_context, receiver=role.user, uploader=uploader, translator=translator, uploaded_contents=uploaded_contents, uploader_message=uploader_message, ) send_email_through(config=self.config, sendmail_callable=email_sender.send_mail, message=message)
def upload_files( self, upload_permission: UploadPermission, uploader_username: str, message: typing.Optional[str], files: typing.List[cgi.FieldStorage], do_notify: bool = False, ) -> typing.List[ContentInContext]: content_api = ContentApi( config=self._config, current_user=upload_permission.author, session=self._session, namespaces_filter=[ContentNamespaces.UPLOAD], ) translator = Translator(app_config=self._config) _ = translator.get_translation current_datetime = datetime.utcnow() folder_label = _( "Files uploaded by {username} on {date} at {time}").format( username=uploader_username, date=format_date(current_datetime, locale=translator.default_lang), time=format_time(current_datetime, locale=translator.default_lang), ) try: upload_folder = content_api.create( content_type_slug=content_type_list.Folder.slug, workspace=upload_permission.workspace, label=folder_label, do_notify=False, do_save=True, content_namespace=ContentNamespaces.UPLOAD, ) except ContentFilenameAlreadyUsedInFolder: upload_folder = content_api.get_one_by_filename( filename=folder_label, workspace=upload_permission.workspace) created_contents = [] if message: comment_message = _("Message from {username}: {message}").format( username=uploader_username, message=message) else: comment_message = _("Uploaded by {username}.").format( username=uploader_username) for _file in files: content = content_api.create( filename=_file.filename, content_type_slug=content_type_list.File.slug, workspace=upload_permission.workspace, parent=upload_folder, do_notify=False, content_namespace=ContentNamespaces.UPLOAD, ) content_api.save(content, ActionDescription.CREATION) with new_revision(session=self._session, tm=transaction.manager, content=content): content_api.update_file_data( content, new_filename=_file.filename, new_mimetype=_file.type, new_content=_file.file, ) content_api.create_comment(parent=content, content=comment_message, do_save=True, do_notify=False) created_contents.append( content_api.get_content_in_context(content)) content_api.execute_created_content_actions(content) if do_notify: workspace_lib = WorkspaceApi(config=self._config, current_user=upload_permission.author, session=self._session) self._notify_uploaded_contents( uploader_username=uploader_username, workspace_in_context=workspace_lib.get_workspace_with_context( upload_permission.workspace), uploader_message=message, uploaded_contents=created_contents, uploader_email=upload_permission.email, ) return created_contents
def notify_created_account( self, user: User, password: str, ) -> None: """ Send created account email to given user. :param password: choosed password :param user: user to notify """ # TODO BS 20160712: Cyclic import logger.debug(self, 'user: {}'.format(user.user_id)) logger.info(self, 'Sending asynchronous email to 1 user ({0})'.format( user.email, )) async_email_sender = EmailSender( self.config, self._smtp_config, self.config.EMAIL_NOTIFICATION_ACTIVATED ) subject = \ self.config.EMAIL_NOTIFICATION_CREATED_ACCOUNT_SUBJECT \ .replace( EST.WEBSITE_TITLE, str(self.config.WEBSITE_TITLE) ) message = MIMEMultipart('alternative') message['Subject'] = subject message['From'] = self._get_sender() message['To'] = formataddr((user.get_display_name(), user.email)) text_template_file_path = self.config.EMAIL_NOTIFICATION_CREATED_ACCOUNT_TEMPLATE_TEXT # nopep8 html_template_file_path = self.config.EMAIL_NOTIFICATION_CREATED_ACCOUNT_TEMPLATE_HTML # nopep8 context = { 'user': user, 'password': password, 'logo_url': get_email_logo_frontend_url(self.config), 'login_url': get_login_frontend_url(self.config), } translator = Translator(self.config, default_lang=user.lang) body_text = self._render_template( mako_template_filepath=text_template_file_path, context=context, translator=translator ) body_html = self._render_template( mako_template_filepath=html_template_file_path, context=context, translator=translator ) part1 = MIMEText(body_text, 'plain', 'utf-8') part2 = MIMEText(body_html, 'html', 'utf-8') # Attach parts into message container. # According to RFC 2046, the last part of a multipart message, # in this case the HTML message, is best and preferred. message.attach(part1) message.attach(part2) send_email_through( config=self.config, sendmail_callable=async_email_sender.send_mail, message=message )
def notify_reset_password( self, user: User, reset_password_token: str, ) -> None: """ Reset password link for user :param user: user to notify :param reset_password_token: token for resetting password """ logger.debug(self, 'user: {}'.format(user.user_id)) logger.info(self, 'Sending asynchronous email to 1 user ({0})'.format( user.email, )) translator = Translator(self.config, default_lang=user.lang) async_email_sender = EmailSender( self.config, self._smtp_config, self.config.EMAIL_NOTIFICATION_ACTIVATED ) subject = self.config.EMAIL_NOTIFICATION_RESET_PASSWORD_SUBJECT.replace( EST.WEBSITE_TITLE, str(self.config.WEBSITE_TITLE) ) message = MIMEMultipart('alternative') message['Subject'] = subject message['From'] = self._get_sender() message['To'] = formataddr((user.get_display_name(), user.email)) text_template_file_path = self.config.EMAIL_NOTIFICATION_RESET_PASSWORD_TEMPLATE_TEXT # nopep8 html_template_file_path = self.config.EMAIL_NOTIFICATION_RESET_PASSWORD_TEMPLATE_HTML # nopep8 # TODO - G.M - 2018-08-17 - Generate token context = { 'user': user, 'logo_url': get_email_logo_frontend_url(self.config), 'reset_password_url': get_reset_password_frontend_url( self.config, token=reset_password_token, email=user.email, ), } body_text = self._render_template( mako_template_filepath=text_template_file_path, context=context, translator=translator, ) body_html = self._render_template( mako_template_filepath=html_template_file_path, context=context, translator=translator, ) part1 = MIMEText(body_text, 'plain', 'utf-8') part2 = MIMEText(body_html, 'html', 'utf-8') # Attach parts into message container. # According to RFC 2046, the last part of a multipart message, # in this case the HTML message, is best and preferred. message.attach(part1) message.attach(part2) send_email_through( config=self.config, sendmail_callable=async_email_sender.send_mail, message=message )
def notify_content_update(self, event_actor_id: int, event_content_id: int) -> None: """ Look for all users to be notified about the new content and send them an individual email :param event_actor_id: id of the user that has triggered the event :param event_content_id: related content_id :return: """ # FIXME - D.A. - 2014-11-05 # Dirty import. It's here in order to avoid circular import from tracim_backend.lib.core.content import ContentApi from tracim_backend.lib.core.user import UserApi user = UserApi(None, config=self.config, session=self.session).get_one(event_actor_id) logger.debug(self, "Content: {}".format(event_content_id)) content_api = ContentApi(current_user=user, session=self.session, config=self.config) content = ContentApi( session=self.session, current_user=user, # TODO - G.M - 2019-04-24 - use a system user instead of the user that has triggered the event config=self.config, show_archived=True, show_deleted=True, ).get_one(event_content_id, content_type_list.Any_SLUG) workspace_api = WorkspaceApi(session=self.session, current_user=user, config=self.config) workpace_in_context = workspace_api.get_workspace_with_context( workspace_api.get_one(content.workspace_id)) main_content = content.parent if content.type == content_type_list.Comment.slug else content notifiable_roles = WorkspaceApi( current_user=user, session=self.session, config=self.config).get_notifiable_roles(content.workspace) if len(notifiable_roles) <= 0: logger.info( self, "Skipping notification as nobody subscribed to in workspace {}" .format(content.workspace.label), ) return logger.info( self, "Generating content {} notification email for {} user(s)".format( content.content_id, len(notifiable_roles)), ) # INFO - D.A. - 2014-11-06 # The following email sender will send emails in the async task queue # This allow to build all mails through current thread but really send them (including SMTP connection) # In the other thread. # # This way, the webserver will return sooner (actually before notification emails are sent email_sender = EmailSender(self.config, self._smtp_config, self.config.EMAIL__NOTIFICATION__ACTIVATED) for role in notifiable_roles: logger.info( self, "Generating content {} notification email to {}".format( content.content_id, role.user.email), ) translator = Translator(app_config=self.config, default_lang=role.user.lang) _ = translator.get_translation # INFO - G.M - 2017-11-15 - set content_id in header to permit reply # references can have multiple values, but only one in this case. replyto_addr = self.config.EMAIL__NOTIFICATION__REPLY_TO__EMAIL.replace( "{content_id}", str(main_content.content_id)) reference_addr = self.config.EMAIL__NOTIFICATION__REFERENCES__EMAIL.replace( "{content_id}", str(main_content.content_id)) # # INFO - D.A. - 2014-11-06 # We do not use .format() here because the subject defined in the .ini file # may not include all required labels. In order to avoid partial format() (which result in an exception) # we do use replace and force the use of .__str__() in order to process LazyString objects # content_status = translator.get_translation( main_content.get_status().label) translated_subject = translator.get_translation( self.config.EMAIL__NOTIFICATION__CONTENT_UPDATE__SUBJECT) subject = translated_subject.replace( EST.WEBSITE_TITLE, self.config.WEBSITE__TITLE.__str__()) subject = subject.replace(EST.WORKSPACE_LABEL, main_content.workspace.label.__str__()) subject = subject.replace(EST.CONTENT_LABEL, main_content.label.__str__()) subject = subject.replace(EST.CONTENT_STATUS_LABEL, content_status) reply_to_label = _( "{username} & all members of {workspace}").format( username=user.display_name, workspace=main_content.workspace.label) content_in_context = content_api.get_content_in_context(content) parent_in_context = None if content.parent_id: parent_in_context = content_api.get_content_in_context( content.parent) body_html = self._build_email_body_for_content( self.config. EMAIL__NOTIFICATION__CONTENT_UPDATE__TEMPLATE__HTML, role, content_in_context, parent_in_context, workpace_in_context, user, translator, ) message = EmailNotificationMessage( subject=subject, from_header=self._get_sender(user), to_header=EmailAddress(role.user.display_name, role.user.email), reply_to=EmailAddress(reply_to_label, replyto_addr), # INFO - G.M - 2017-11-15 # References can theorically have label, but in pratice, references # contains only message_id from parents post in thread. # To link this email to a content we create a virtual parent # in reference who contain the content_id. # INFO - G.M - 2020-04-03 - Enforce angle bracket in references header # we need that to ensure best software compatibility # compat from parsing software references=EmailAddress("", reference_addr, force_angle_bracket=True), body_html=body_html, lang=translator.default_lang, ) self.log_email_notification( msg="an email was created to {}".format(message["To"]), action="{:8s}".format("CREATED"), email_recipient=message["To"], email_subject=message["Subject"], config=self.config, ) send_email_through(self.config, email_sender.send_mail, message)
def notify_content_update( self, event_actor_id: int, event_content_id: int ) -> None: """ Look for all users to be notified about the new content and send them an individual email :param event_actor_id: id of the user that has triggered the event :param event_content_id: related content_id :return: """ # FIXME - D.A. - 2014-11-05 # Dirty import. It's here in order to avoid circular import from tracim_backend.lib.core.content import ContentApi from tracim_backend.lib.core.user import UserApi user = UserApi( None, config=self.config, session=self.session, ).get_one(event_actor_id) logger.debug(self, 'Content: {}'.format(event_content_id)) content_api = ContentApi( current_user=user, session=self.session, config=self.config, ) content = ContentApi( session=self.session, current_user=user, # nopep8 TODO - use a system user instead of the user that has triggered the event config=self.config, show_archived=True, show_deleted=True, ).get_one(event_content_id, content_type_list.Any_SLUG) workspace_api = WorkspaceApi( session=self.session, current_user=user, config=self.config, ) workpace_in_context = workspace_api.get_workspace_with_context(workspace_api.get_one(content.workspace_id)) # nopep8 main_content = content.parent if content.type == content_type_list.Comment.slug else content # nopep8 notifiable_roles = WorkspaceApi( current_user=user, session=self.session, config=self.config, ).get_notifiable_roles(content.workspace) if len(notifiable_roles) <= 0: logger.info(self, 'Skipping notification as nobody subscribed to in workspace {}'.format(content.workspace.label)) return logger.info(self, 'Generating content {} notification email for {} user(s)'.format( content.content_id, len(notifiable_roles) )) # INFO - D.A. - 2014-11-06 # The following email sender will send emails in the async task queue # This allow to build all mails through current thread but really send them (including SMTP connection) # In the other thread. # # This way, the webserver will return sooner (actually before notification emails are sent email_sender = EmailSender( self.config, self._smtp_config, self.config.EMAIL_NOTIFICATION_ACTIVATED ) for role in notifiable_roles: logger.info(self, 'Generating content {} notification email to {}'.format( content.content_id, role.user.email ) ) translator = Translator(app_config=self.config, default_lang=role.user.lang) # nopep8 _ = translator.get_translation to_addr = formataddr((role.user.display_name, role.user.email)) # INFO - G.M - 2017-11-15 - set content_id in header to permit reply # references can have multiple values, but only one in this case. replyto_addr = self.config.EMAIL_NOTIFICATION_REPLY_TO_EMAIL.replace( # nopep8 '{content_id}', str(main_content.content_id) ) reference_addr = self.config.EMAIL_NOTIFICATION_REFERENCES_EMAIL.replace( #nopep8 '{content_id}',str(main_content.content_id) ) # # INFO - D.A. - 2014-11-06 # We do not use .format() here because the subject defined in the .ini file # may not include all required labels. In order to avoid partial format() (which result in an exception) # we do use replace and force the use of .__str__() in order to process LazyString objects # content_status = translator.get_translation(main_content.get_status().label) translated_subject = translator.get_translation(self.config.EMAIL_NOTIFICATION_CONTENT_UPDATE_SUBJECT) subject = translated_subject.replace(EST.WEBSITE_TITLE, self.config.WEBSITE_TITLE.__str__()) subject = subject.replace(EST.WORKSPACE_LABEL, main_content.workspace.label.__str__()) subject = subject.replace(EST.CONTENT_LABEL, main_content.label.__str__()) subject = subject.replace(EST.CONTENT_STATUS_LABEL, content_status) reply_to_label = _('{username} & all members of {workspace}').format( # nopep8 username=user.display_name, workspace=main_content.workspace.label) message = MIMEMultipart('alternative') message['Subject'] = subject message['From'] = self._get_sender(user) message['To'] = to_addr message['Reply-to'] = formataddr((reply_to_label, replyto_addr)) # INFO - G.M - 2017-11-15 # References can theorically have label, but in pratice, references # contains only message_id from parents post in thread. # To link this email to a content we create a virtual parent # in reference who contain the content_id. message['References'] = formataddr(('', reference_addr)) content_in_context = content_api.get_content_in_context(content) parent_in_context = None if content.parent_id: parent_in_context = content_api.get_content_in_context(content.parent) # nopep8 body_html = self._build_email_body_for_content( self.config.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_HTML, role, content_in_context, parent_in_context, workpace_in_context, user, translator, ) part2 = MIMEText(body_html, 'html', 'utf-8') # Attach parts into message container. # According to RFC 2046, the last part of a multipart message, in this case # the HTML message, is best and preferred. message.attach(part2) self.log_email_notification( msg='an email was created to {}'.format(message['To']), action='{:8s}'.format('CREATED'), email_recipient=message['To'], email_subject=message['Subject'], config=self.config, ) send_email_through( self.config, email_sender.send_mail, message )
def _build_context_for_content_update( self, role: UserRoleInWorkspace, content_in_context: ContentInContext, parent_in_context: typing.Optional[ContentInContext], workspace_in_context: WorkspaceInContext, actor: User, translator: Translator ): _ = translator.get_translation content = content_in_context.content action = content.get_last_action().id # default values user = role.user workspace = role.workspace workspace_url = workspace_in_context.frontend_url main_title = content.label status_label = content.get_status().label # TODO - G.M - 11-06-2018 - [emailTemplateURL] correct value for status_icon_url # nopep8 status_icon_url = '' role_label = role.role_as_label() content_intro = '<span id="content-intro-username">{}</span> did something.'.format(actor.display_name) # nopep8 content_text = content.description call_to_action_url = content_in_context.frontend_url logo_url = get_email_logo_frontend_url(self.config) if ActionDescription.COMMENT == action: main_title = parent_in_context.label content_intro = '' call_to_action_url = parent_in_context.frontend_url elif ActionDescription.STATUS_UPDATE == action: new_status = translator.get_translation(content.get_status().label) main_title = content_in_context.label content_intro = _('I modified the status of <i>{content}</i>. The new status is <i>{new_status}</i>').format( content=content.get_label(), new_status=new_status ) content_text = '' call_to_action_url = content_in_context.frontend_url elif ActionDescription.CREATION == action: main_title = content_in_context.label content_intro = _('I added an item entitled <i>{content}</i>.').format(content=content.get_label()) # nopep8 content_text = '' elif action in (ActionDescription.REVISION, ActionDescription.EDITION): main_title = content_in_context.label content_intro = _('I updated <i>{content}</i>.').format(content=content.get_label()) # nopep8 previous_revision = content.get_previous_revision() title_diff = htmldiff(previous_revision.label, content.label) content_diff = htmldiff(previous_revision.description, content.description) if title_diff or content_diff: content_text = str('<p>{diff_intro_text}</p>\n{title_diff}\n{content_diff}').format( # nopep8 diff_intro_text=_('Here is an overview of the changes:'), title_diff=title_diff, content_diff=content_diff ) # if not content_intro and not content_text: # # Skip notification, but it's not normal # logger.error( # self, # 'A notification is being sent but no content. ' # 'Here are some debug informations: [content_id: {cid}]' # '[action: {act}][author: {actor}]'.format( # cid=content.content_id, # act=action, # actor=actor # ) # ) # raise EmptyNotificationError('Unexpected empty notification') # FIXME: remove/readapt assert to debug easily broken case assert user assert workspace assert main_title assert status_label # assert status_icon_url assert role_label # assert content_intro assert content_text or content_text == content.description assert call_to_action_url assert logo_url return { 'user': role.user, 'workspace': role.workspace, 'workspace_url': workspace_url, 'main_title': main_title, 'status_label': status_label, 'status_icon_url': status_icon_url, 'role_label': role_label, 'content_intro': content_intro, 'content_text': content_text, 'call_to_action_url': call_to_action_url, 'logo_url': logo_url, }