Example #1
0
    def _create_content_event(self, operation: OperationType, content: Content,
                              context: TracimContext) -> None:
        current_user = context.safe_current_user()
        content_api = ContentApi(context.dbsession, current_user, self._config)
        content_in_context = content_api.get_content_in_context(content)
        content_schema = EventApi.get_content_schema_for_type(content.type)
        content_dict = content_schema.dump(content_in_context).data

        workspace_api = WorkspaceApi(context.dbsession,
                                     current_user,
                                     self._config,
                                     show_deleted=True)
        workspace_in_context = workspace_api.get_workspace_with_context(
            workspace_api.get_one(content_in_context.workspace.workspace_id))
        fields = {
            Event.CONTENT_FIELD:
            content_dict,
            Event.WORKSPACE_FIELD:
            EventApi.workspace_schema.dump(workspace_in_context).data,
        }
        event_api = EventApi(current_user, context.dbsession, self._config)
        event_api.create_event(
            entity_type=EntityType.CONTENT,
            operation=operation,
            additional_fields=fields,
            entity_subtype=content.type,
            context=context,
        )
Example #2
0
 def create_workspace(self,
                      context,
                      request: TracimRequest,
                      hapic_data=None):
     """
     Create a workspace. This route is for trusted users and administrators.
     """
     app_config = request.registry.settings["CFG"]  # type: CFG
     wapi = WorkspaceApi(
         current_user=request.current_user,
         session=request.dbsession,
         config=app_config  # User
     )
     parent = None
     if hapic_data.body.parent_id:
         parent = wapi.get_one(workspace_id=hapic_data.body.parent_id)
     workspace = wapi.create_workspace(
         label=hapic_data.body.label,
         description=hapic_data.body.description,
         access_type=hapic_data.body.access_type,
         save_now=True,
         agenda_enabled=hapic_data.body.agenda_enabled,
         public_download_enabled=hapic_data.body.public_download_enabled,
         public_upload_enabled=hapic_data.body.public_upload_enabled,
         default_user_role=hapic_data.body.default_user_role,
         parent=parent,
     )
     wapi.execute_created_workspace_actions(workspace)
     return wapi.get_workspace_with_context(workspace)
Example #3
0
 def _create_subscription_event(self, operation: OperationType,
                                subscription: WorkspaceSubscription,
                                context: TracimContext) -> None:
     current_user = context.safe_current_user()
     workspace_api = WorkspaceApi(
         session=context.dbsession,
         config=self._config,
         current_user=None,
     )
     workspace_in_context = workspace_api.get_workspace_with_context(
         workspace_api.get_one(subscription.workspace_id))
     user_api = UserApi(current_user,
                        context.dbsession,
                        self._config,
                        show_deleted=True)
     subscription_author_in_context = user_api.get_user_with_context(
         subscription.author)
     fields = {
         Event.WORKSPACE_FIELD:
         EventApi.workspace_schema.dump(workspace_in_context).data,
         Event.SUBSCRIPTION_FIELD:
         EventApi.workspace_subscription_schema.dump(subscription).data,
         Event.USER_FIELD:
         EventApi.user_schema.dump(subscription_author_in_context).data,
     }
     event_api = EventApi(current_user, context.dbsession, self._config)
     event_api.create_event(
         entity_type=EntityType.WORKSPACE_SUBSCRIPTION,
         operation=operation,
         additional_fields=fields,
         context=context,
     )
Example #4
0
 def update_workspace(self,
                      context,
                      request: TracimRequest,
                      hapic_data=None):
     """
     Update a workspace. This route is for trusted users and administrators.
     Note : a trusted user can only update spaces on which he/she is space manager
     """
     app_config = request.registry.settings["CFG"]  # type: CFG
     wapi = WorkspaceApi(
         current_user=request.current_user,
         session=request.dbsession,
         config=app_config  # User
     )
     wapi.update_workspace(
         request.current_workspace,
         label=hapic_data.body.label,
         description=hapic_data.body.description,
         agenda_enabled=hapic_data.body.agenda_enabled,
         public_download_enabled=hapic_data.body.public_download_enabled,
         public_upload_enabled=hapic_data.body.public_upload_enabled,
         default_user_role=hapic_data.body.default_user_role,
         save_now=True,
     )
     wapi.execute_update_workspace_actions(request.current_workspace)
     return wapi.get_workspace_with_context(request.current_workspace)
Example #5
0
 def workspace(self, context, request: TracimRequest, hapic_data=None):
     """
     Get workspace informations
     """
     app_config = request.registry.settings['CFG']
     wapi = WorkspaceApi(
         current_user=request.current_user,  # User
         session=request.dbsession,
         config=app_config,
     )
     return wapi.get_workspace_with_context(request.current_workspace)
Example #6
0
 def workspace(self, context, request: TracimRequest, hapic_data=None):
     """
     Get workspace informations
     """
     app_config = request.registry.settings['CFG']
     wapi = WorkspaceApi(
         current_user=request.current_user,  # User
         session=request.dbsession,
         config=app_config,
     )
     return wapi.get_workspace_with_context(request.current_workspace)
Example #7
0
 def workspace_disk_space(self,
                          context,
                          request: TracimRequest,
                          hapic_data=None):
     """
     Get workspace space info
     """
     app_config = request.registry.settings["CFG"]  # type: CFG
     wapi = WorkspaceApi(
         current_user=request.current_user,
         session=request.dbsession,
         config=app_config  # User
     )
     return wapi.get_workspace_with_context(request.current_workspace)
Example #8
0
 def join_workspace(self, context, request: TracimRequest, hapic_data=None):
     """
     Join a workspace.
     Only possible for OPEN workspaces.
     Subscribing to a ON_REQUEST workspace is done through /api/users/<user_id>/workspace_subscriptions.
     """
     app_config = request.registry.settings["CFG"]  # type: CFG
     wapi = WorkspaceApi(
         current_user=request.candidate_user,  # User
         session=request.dbsession,
         config=app_config,
     )
     workspace = wapi.add_current_user_as_member(workspace_id=hapic_data.body["workspace_id"])
     return wapi.get_workspace_with_context(workspace)
Example #9
0
    def get_accessible_workspaces(
        self, context, request: TracimRequest, hapic_data: HapicData
    ) -> typing.List[WorkspaceInContext]:
        """
        Return the list of accessible workspaces by the given user id.
        An accessible workspace is:
          - a workspace the user is not member of (`workspaces` API returns them)
          - has an OPEN or ON_REQUEST access type
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        wapi = WorkspaceApi(
            current_user=request.candidate_user, session=request.dbsession, config=app_config,
        )

        workspaces = wapi.get_all_accessible_by_user(request.candidate_user)
        return [wapi.get_workspace_with_context(workspace) for workspace in workspaces]
Example #10
0
 def create_workspace(self, context, request: TracimRequest, hapic_data=None):  # nopep8
     """
     Create a workspace. This route is for trusted users and administrators.
     """
     app_config = request.registry.settings['CFG']
     wapi = WorkspaceApi(
         current_user=request.current_user,  # User
         session=request.dbsession,
         config=app_config,
     )
     workspace = wapi.create_workspace(
         label=hapic_data.body.label,
         description=hapic_data.body.description,
         save_now=True,
     )
     return wapi.get_workspace_with_context(workspace)
Example #11
0
    def account_workspace(self, context, request: TracimRequest, hapic_data=None):
        """
        Get list of auth user workspaces
        """
        app_config = request.registry.settings['CFG']
        wapi = WorkspaceApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
        )

        workspaces = wapi.get_all_for_user(request.current_user)
        return [
            wapi.get_workspace_with_context(workspace)
            for workspace in workspaces
        ]
Example #12
0
    def user_workspace(self, context, request: TracimRequest, hapic_data=None):
        """
        Get list of user workspaces
        """
        app_config = request.registry.settings['CFG']
        wapi = WorkspaceApi(
            current_user=request.candidate_user,  # User
            session=request.dbsession,
            config=app_config,
        )

        workspaces = wapi.get_all_for_user(request.candidate_user)
        return [
            wapi.get_workspace_with_context(workspace)
            for workspace in workspaces
        ]
Example #13
0
    def _create_role_event(self, operation: OperationType,
                           role: UserRoleInWorkspace,
                           context: TracimContext) -> None:
        current_user = context.safe_current_user()
        workspace_api = WorkspaceApi(
            session=context.dbsession,
            config=self._config,
            show_deleted=True,
            # INFO - G.M - 2020-17-09 - we do explicitly don't set user here to not
            # have filter on workspace, in some case we do want user create event on workspace
            # he doesn't have access: when he remove itself from workspace for example
            current_user=None,
        )
        workspace_in_context = workspace_api.get_workspace_with_context(
            workspace_api.get_one(role.workspace_id))
        user_api = UserApi(current_user,
                           context.dbsession,
                           self._config,
                           show_deleted=True)
        role_api = RoleApi(current_user=current_user,
                           session=context.dbsession,
                           config=self._config)
        try:
            user_field = EventApi.user_schema.dump(
                user_api.get_user_with_context(user_api.get_one(
                    role.user_id))).data
        except UserDoesNotExist:
            # It is possible to have an already deleted user when deleting his roles.
            user_field = None

        role_in_context = role_api.get_user_role_workspace_with_context(role)
        fields = {
            Event.USER_FIELD:
            user_field,
            Event.WORKSPACE_FIELD:
            EventApi.workspace_schema.dump(workspace_in_context).data,
            Event.MEMBER_FIELD:
            EventApi.workspace_user_role_schema.dump(role_in_context).data,
        }
        event_api = EventApi(current_user, context.dbsession, self._config)
        event_api.create_event(
            entity_type=EntityType.WORKSPACE_MEMBER,
            operation=operation,
            additional_fields=fields,
            context=context,
        )
Example #14
0
    def workspaces(self, context, request: TracimRequest, hapic_data=None):
        """
        Returns the list of all workspaces. This route is for admin only.
        Standard users must use their own route: /api/v2/users/me/workspaces
        """
        app_config = request.registry.settings['CFG']
        wapi = WorkspaceApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
        )

        workspaces = wapi.get_all()
        return [
            wapi.get_workspace_with_context(workspace)
            for workspace in workspaces
        ]
Example #15
0
    def workspaces(self, context, request: TracimRequest, hapic_data=None):
        """
        Returns the list of all workspaces. This route is for admin only.
        Standard users must use their own route: /api/v2/users/me/workspaces
        """
        app_config = request.registry.settings['CFG']
        wapi = WorkspaceApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
        )

        workspaces = wapi.get_all()
        return [
            wapi.get_workspace_with_context(workspace)
            for workspace in workspaces
        ]
Example #16
0
    def user_workspace(self, context, request: TracimRequest, hapic_data=None):
        """
        Get list of user workspaces
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        wapi = WorkspaceApi(
            current_user=request.candidate_user,  # User
            session=request.dbsession,
            config=app_config,
        )

        workspaces = wapi.get_all_for_user(
            request.candidate_user,
            include_owned=hapic_data.query.show_owned_workspace,
            include_with_role=hapic_data.query.show_workspace_with_role,
            parents_ids=hapic_data.query.parent_ids,
        )
        return [wapi.get_workspace_with_context(workspace) for workspace in workspaces]
Example #17
0
 def update_workspace(self, context, request: TracimRequest, hapic_data=None):  # nopep8
     """
     Update a workspace. This route is for trusted users and administrators.
     Note : a trusted user can only update spaces on which he/she is space manager
     """
     app_config = request.registry.settings['CFG']
     wapi = WorkspaceApi(
         current_user=request.current_user,  # User
         session=request.dbsession,
         config=app_config,
     )
     wapi.update_workspace(
         request.current_workspace,
         label=hapic_data.body.label,
         description=hapic_data.body.description,
         save_now=True,
     )
     return wapi.get_workspace_with_context(request.current_workspace)
Example #18
0
    def account_workspace(self,
                          context,
                          request: TracimRequest,
                          hapic_data=None):
        """
        Get list of auth user workspaces
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        wapi = WorkspaceApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config  # User
        )

        workspaces = wapi.get_all_for_user(request.current_user)
        return [
            wapi.get_workspace_with_context(workspace)
            for workspace in workspaces
        ]
Example #19
0
 def _create_workspace_event(self, operation: OperationType,
                             workspace: Workspace,
                             context: TracimContext) -> None:
     current_user = context.safe_current_user()
     api = WorkspaceApi(current_user=current_user,
                        session=context.dbsession,
                        config=self._config)
     workspace_in_context = api.get_workspace_with_context(workspace)
     fields = {
         Event.WORKSPACE_FIELD:
         EventApi.workspace_schema.dump(workspace_in_context).data
     }
     event_api = EventApi(current_user, context.dbsession, self._config)
     event_api.create_event(
         entity_type=EntityType.WORKSPACE,
         operation=operation,
         additional_fields=fields,
         context=context,
     )
Example #20
0
    def _create_mention_events(
        cls, mentions: typing.Iterable[Mention], content: Content, context: TracimContext
    ) -> None:
        role_api = RoleApi(session=context.dbsession, config=context.app_config, current_user=None)

        workspace_members_usernames = [
            user.username for user in role_api.get_workspace_members(content.workspace_id)
        ]

        for mention in mentions:
            recipient = mention.recipient
            if (
                recipient not in workspace_members_usernames
                and recipient not in ALL__GROUP_MENTIONS
            ):
                raise UserNotMemberOfWorkspace(
                    "This user is not a member of this workspace: {}".format(mention.recipient)
                )

        current_user = context.safe_current_user()
        content_api = ContentApi(context.dbsession, current_user, context.app_config)
        content_in_context = content_api.get_content_in_context(content)
        workspace_api = WorkspaceApi(context.dbsession, current_user, context.app_config)
        workspace_in_context = workspace_api.get_workspace_with_context(
            workspace_api.get_one(content_in_context.workspace.workspace_id)
        )
        content_schema = EventApi.get_content_schema_for_type(content.type)
        content_dict = content_schema.dump(content_in_context).data
        common_fields = {
            Event.CONTENT_FIELD: content_dict,
            Event.WORKSPACE_FIELD: EventApi.workspace_schema.dump(workspace_in_context).data,
        }

        event_api = EventApi(current_user, context.dbsession, context.app_config)
        for mention in mentions:
            fields = {cls.MENTION_FIELD: {"recipient": mention.recipient, "id": mention.id}}
            fields.update(common_fields)
            event_api.create_event(
                entity_type=EntityType.MENTION,
                operation=OperationType.CREATED,
                additional_fields=fields,
                context=context,
            )
Example #21
0
    def workspaces(self, context, request: TracimRequest, hapic_data=None):
        """
        Returns the list of all workspaces. This route is for admin only.
        Standard users must use their own route: /api/users/me/workspaces
        Filtering by parent_ids is possible through this endpoint
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        wapi = WorkspaceApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config  # User
        )

        workspaces = wapi.get_all_children(
            parent_ids=hapic_data.query.parent_ids)
        return [
            wapi.get_workspace_with_context(workspace)
            for workspace in workspaces
        ]
Example #22
0
 def create_workspace(self,
                      context,
                      request: TracimRequest,
                      hapic_data=None):
     """
     Create a workspace. This route is for trusted users and administrators.
     """
     app_config = request.registry.settings["CFG"]  # type: CFG
     wapi = WorkspaceApi(
         current_user=request.current_user,
         session=request.dbsession,
         config=app_config  # User
     )
     workspace = wapi.create_workspace(
         label=hapic_data.body.label,
         description=hapic_data.body.description,
         save_now=True,
         agenda_enabled=hapic_data.body.agenda_enabled,
     )
     wapi.execute_created_workspace_actions(workspace)
     return wapi.get_workspace_with_context(workspace)
Example #23
0
 def workspace(self) -> WorkspaceInContext:
     workspace_api = WorkspaceApi(config=self.config, session=self.dbsession, current_user=None)
     return workspace_api.get_workspace_with_context(self.upload_permission.workspace)
Example #24
0
    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
Example #25
0
    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
            )
Example #26
0
    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)