Exemplo n.º 1
0
class ActionDescription(object):
    """
    Allowed status are:
    - open
    - closed-validated
    - closed-invalidated
    - closed-deprecated
    """

    ARCHIVING = 'archiving'
    COMMENT = 'content-comment'
    CREATION = 'creation'
    DELETION = 'deletion'
    EDITION = 'edition'  # Default action if unknow
    REVISION = 'revision'
    STATUS_UPDATE = 'status-update'
    UNARCHIVING = 'unarchiving'
    UNDELETION = 'undeletion'
    MOVE = 'move'

    _ICONS = {
        'archiving': 'fa fa-archive',
        'content-comment': 'fa-comment-o',
        'creation': 'fa-magic',
        'deletion': 'fa-trash',
        'edition': 'fa-edit',
        'revision': 'fa-history',
        'status-update': 'fa-random',
        'unarchiving': 'fa-file-archive-o',
        'undeletion': 'fa-trash-o',
        'move': 'fa-arrows'
    }

    _LABELS = {
        'archiving': l_('archive'),
        'content-comment': l_('Item commented'),
        'creation': l_('Item created'),
        'deletion': l_('Item deleted'),
        'edition': l_('item modified'),
        'revision': l_('New revision'),
        'status-update': l_('New status'),
        'unarchiving': l_('Item unarchived'),
        'undeletion': l_('Item undeleted'),
        'move': l_('Item moved')
    }

    def __init__(self, id):
        assert id in ActionDescription.allowed_values()
        self.id = id
        self.label = ActionDescription._LABELS[id]
        self.icon = ActionDescription._ICONS[id]
        self.css = ''

    @classmethod
    def allowed_values(cls):
        return [
            cls.ARCHIVING, cls.COMMENT, cls.CREATION, cls.DELETION,
            cls.EDITION, cls.REVISION, cls.STATUS_UPDATE, cls.UNARCHIVING,
            cls.UNDELETION, cls.MOVE
        ]
Exemplo n.º 2
0
Arquivo: data.py Projeto: feth/tracim
class UserRoleInWorkspace(DeclarativeBase):

    __tablename__ = 'user_workspace'

    user_id = Column(Integer, ForeignKey('users.user_id'), nullable=False, default=None, primary_key=True)
    workspace_id = Column(Integer, ForeignKey('workspaces.workspace_id'), nullable=False, default=None, primary_key=True)
    role = Column(Integer, nullable=False, default=0, primary_key=False)
    do_notify = Column(Boolean, unique=False, nullable=False, default=False)

    workspace = relationship('Workspace', remote_side=[Workspace.workspace_id], backref='roles', lazy='joined')
    user = relationship('User', remote_side=[User.user_id], backref='roles')

    NOT_APPLICABLE = 0
    READER = 1
    CONTRIBUTOR = 2
    CONTENT_MANAGER = 4
    WORKSPACE_MANAGER = 8

    LABEL = dict()
    LABEL[0] = l_('N/A')
    LABEL[1] = l_('Reader')
    LABEL[2] = l_('Contributor')
    LABEL[4] = l_('Content Manager')
    LABEL[8] = l_('Workspace Manager')

    STYLE = dict()
    STYLE[0] = ''
    STYLE[1] = 'color: #1fdb11;'
    STYLE[2] = 'color: #759ac5;'
    STYLE[4] = 'color: #ea983d;'
    STYLE[8] = 'color: #F00;'

    ICON = dict()
    ICON[0] = ''
    ICON[1] = 'fa-eye'
    ICON[2] = 'fa-pencil'
    ICON[4] = 'fa-graduation-cap'
    ICON[8] = 'fa-legal'


    @property
    def icon(self):
        return UserRoleInWorkspace.ICON[self.role]

    @property
    def style(self):
        return UserRoleInWorkspace.STYLE[self.role]

    def role_as_label(self):
        return UserRoleInWorkspace.LABEL[self.role]

    @classmethod
    def get_all_role_values(self):
        return [
            UserRoleInWorkspace.READER,
            UserRoleInWorkspace.CONTRIBUTOR,
            UserRoleInWorkspace.CONTENT_MANAGER,
            UserRoleInWorkspace.WORKSPACE_MANAGER
        ]
Exemplo n.º 3
0
class Profile(object):
    """This model is the "max" group associated to a given user."""

    _NAME = [
        Group.TIM_NOBODY_GROUPNAME, Group.TIM_USER_GROUPNAME,
        Group.TIM_MANAGER_GROUPNAME, Group.TIM_ADMIN_GROUPNAME
    ]

    _LABEL = [
        l_('Nobody'),
        l_('Users'),
        l_('Global managers'),
        l_('Administrators')
    ]

    def __init__(self, profile_id):
        assert isinstance(profile_id, int)
        self.id = profile_id
        self.name = Profile._NAME[profile_id]
        self.label = Profile._LABEL[profile_id]
Exemplo n.º 4
0
class Globals(object):
    """Container for objects available throughout the life of the application.

    One instance of Globals is created during application initialization and
    is available during requests via the 'app_globals' variable.

    """
    def __init__(self):
        """Do nothing, by default."""
        pass

    VERSION_NUMBER = '1.0.3'
    SHORT_DATE_FORMAT = l_('%B %d at %I:%M%p')
Exemplo n.º 5
0
    # Don't start daemons if they are disabled
    if config.get('disable_daemons', False):
        return

    manager.run('radicale', RadicaleDaemon)
    manager.run('webdav', WsgiDavDaemon)

    if cfg.EMAIL_PROCESSING_MODE == CFG.CST.ASYNC:
        manager.run('mail_sender', MailSenderDaemon)


environment_loaded.register(lambda: start_daemons(daemons))
interrupt_manager = InterruptManager(os.getpid(), daemons_manager=daemons)

# Note: here are fake translatable strings that allow to translate messages for reset password email content
duplicated_email_subject = l_('Password reset request')
duplicated_email_body = l_('''
We've received a request to reset the password for this account.
Please click this link to reset your password:

%(password_reset_link)s

If you no longer wish to make the above change, or if you did not initiate this request, please disregard and/or delete this e-mail.
''')

#######
#
# INFO - D.A. - 2014-10-31
# The following code is a dirty way to integrate translation for resetpassword tgapp in tracim
# TODO - Integrate these translations into tgapp-resetpassword
#
Exemplo n.º 6
0
class ContentType(object):
    Any = 'any'

    Folder = 'folder'
    File = 'file'
    Comment = 'comment'
    Thread = 'thread'
    Page = 'page'
    Event = 'event'

    # Fake types, used for breadcrumb only
    FAKE_Dashboard = 'dashboard'
    FAKE_Workspace = 'workspace'

    _STRING_LIST_SEPARATOR = ','

    _ICONS = {  # Deprecated
        'dashboard': 'fa-home',
        'workspace': 'fa-bank',
        'folder': 'fa fa-folder-open-o',
        'file': 'fa fa-paperclip',
        'page': 'fa fa-file-text-o',
        'thread': 'fa fa-comments-o',
        'comment': 'fa fa-comment-o',
        'event': 'fa fa-calendar-o',
    }

    _CSS_ICONS = {
        'dashboard': 'fa fa-home',
        'workspace': 'fa fa-bank',
        'folder': 'fa fa-folder-open-o',
        'file': 'fa fa-paperclip',
        'page': 'fa fa-file-text-o',
        'thread': 'fa fa-comments-o',
        'comment': 'fa fa-comment-o',
        'event': 'fa fa-calendar-o',
    }

    _CSS_COLORS = {
        'dashboard': 't-dashboard-color',
        'workspace': 't-less-visible',
        'folder': 't-folder-color',
        'file': 't-file-color',
        'page': 't-page-color',
        'thread': 't-thread-color',
        'comment': 't-thread-color',
        'event': 't-event-color',
    }

    _ORDER_WEIGHT = {
        'folder': 0,
        'page': 1,
        'thread': 2,
        'file': 3,
        'comment': 4,
        'event': 5,
    }

    _LABEL = {
        'dashboard': '',
        'workspace': l_('workspace'),
        'folder': l_('folder'),
        'file': l_('file'),
        'page': l_('page'),
        'thread': l_('thread'),
        'comment': l_('comment'),
        'event': l_('event'),
    }

    _DELETE_LABEL = {
        'dashboard': '',
        'workspace': l_('Delete this workspace'),
        'folder': l_('Delete this folder'),
        'file': l_('Delete this file'),
        'page': l_('Delete this page'),
        'thread': l_('Delete this thread'),
        'comment': l_('Delete this comment'),
        'event': l_('Delete this event'),
    }

    @classmethod
    def get_icon(cls, type: str):
        assert (type in ContentType._ICONS)  # DYN_REMOVE
        return ContentType._ICONS[type]

    @classmethod
    def all(cls):
        return cls.allowed_types()

    @classmethod
    def allowed_types(cls):
        return [
            cls.Folder, cls.File, cls.Comment, cls.Thread, cls.Page, cls.Event
        ]

    @classmethod
    def allowed_types_for_folding(cls):
        # This method is used for showing only "main" types in the left-side treeview
        return [cls.Folder, cls.File, cls.Thread, cls.Page]

    @classmethod
    def allowed_types_from_str(cls, allowed_types_as_string: str):
        allowed_types = []
        # HACK - THIS
        for item in allowed_types_as_string.split(
                ContentType._STRING_LIST_SEPARATOR):
            if item and item in ContentType.allowed_types_for_folding():
                allowed_types.append(item)
        return allowed_types

    @classmethod
    def fill_url(cls, content: 'Content'):
        # TODO - DYNDATATYPE - D.A. - 2014-12-02
        # Make this code dynamic loading data types

        if content.type == ContentType.Folder:
            return '/workspaces/{}/folders/{}'.format(content.workspace_id,
                                                      content.content_id)
        elif content.type == ContentType.File:
            return '/workspaces/{}/folders/{}/files/{}'.format(
                content.workspace_id, content.parent_id, content.content_id)
        elif content.type == ContentType.Thread:
            return '/workspaces/{}/folders/{}/threads/{}'.format(
                content.workspace_id, content.parent_id, content.content_id)
        elif content.type == ContentType.Page:
            return '/workspaces/{}/folders/{}/pages/{}'.format(
                content.workspace_id, content.parent_id, content.content_id)

    @classmethod
    def fill_url_for_workspace(cls, workspace: Workspace):
        # TODO - DYNDATATYPE - D.A. - 2014-12-02
        # Make this code dynamic loading data types
        return '/workspaces/{}'.format(workspace.workspace_id)

    @classmethod
    def sorted(cls, types: ['ContentType']) -> ['ContentType']:
        return sorted(types, key=lambda content_type: content_type.priority)

    @property
    def type(self):
        return self.id

    def __init__(self, type):
        self.id = type
        self.icon = ContentType._CSS_ICONS[type]
        self.color = ContentType._CSS_COLORS[type]  # deprecated
        self.css = ContentType._CSS_COLORS[type]
        self.label = ContentType._LABEL[type]
        self.priority = ContentType._ORDER_WEIGHT[type]

    def toDict(self):
        return dict(id=self.type,
                    type=self.type,
                    icon=self.icon,
                    color=self.color,
                    label=self.label,
                    priority=self.priority)
Exemplo n.º 7
0
class ContentStatus(object):
    """
    Allowed status are:
    - open
    - closed-validated
    - closed-invalidated
    - closed-deprecated
    """

    OPEN = 'open'
    CLOSED_VALIDATED = 'closed-validated'
    CLOSED_UNVALIDATED = 'closed-unvalidated'
    CLOSED_DEPRECATED = 'closed-deprecated'

    _LABELS = {
        'open': l_('work in progress'),
        'closed-validated': l_('closed — validated'),
        'closed-unvalidated': l_('closed — cancelled'),
        'closed-deprecated': l_('deprecated')
    }

    _LABELS_THREAD = {
        'open': l_('subject in progress'),
        'closed-validated': l_('subject closed — resolved'),
        'closed-unvalidated': l_('subject closed — cancelled'),
        'closed-deprecated': l_('deprecated')
    }

    _LABELS_FILE = {
        'open': l_('work in progress'),
        'closed-validated': l_('closed — validated'),
        'closed-unvalidated': l_('closed — cancelled'),
        'closed-deprecated': l_('deprecated')
    }

    _ICONS = {
        'open': 'fa fa-square-o',
        'closed-validated': 'fa fa-check-square-o',
        'closed-unvalidated': 'fa fa-close',
        'closed-deprecated': 'fa fa-warning',
    }

    _CSS = {
        'open': 'tracim-status-open',
        'closed-validated': 'tracim-status-closed-validated',
        'closed-unvalidated': 'tracim-status-closed-unvalidated',
        'closed-deprecated': 'tracim-status-closed-deprecated',
    }

    def __init__(self, id, type=''):
        self.id = id
        self.icon = ContentStatus._ICONS[id]
        self.css = ContentStatus._CSS[id]

        if type == ContentType.Thread:
            self.label = ContentStatus._LABELS_THREAD[id]
        elif type == ContentType.File:
            self.label = ContentStatus._LABELS_FILE[id]
        else:
            self.label = ContentStatus._LABELS[id]

    @classmethod
    def all(cls, type='') -> ['ContentStatus']:
        all = []
        all.append(ContentStatus('open', type))
        all.append(ContentStatus('closed-validated', type))
        all.append(ContentStatus('closed-unvalidated', type))
        all.append(ContentStatus('closed-deprecated', type))
        return all

    @classmethod
    def allowed_values(cls):
        return ContentStatus._LABELS.keys()
Exemplo n.º 8
0
    def _build_email_body(self, mako_template_filepath: str,
                          role: UserRoleInWorkspace, content: Content,
                          actor: User) -> str:
        """
        Build an email body and return it as a string
        :param mako_template_filepath: the absolute path to the mako template to be used for email body building
        :param role: the role related to user to whom the email must be sent. The role is required (and not the user only) in order to show in the mail why the user receive the notification
        :param content: the content item related to the notification
        :param actor: the user at the origin of the action / notification (for example the one who wrote a comment
        :param config: the global configuration
        :return: the built email body as string. In case of multipart email, this method must be called one time for text and one time for html
        """
        logger.debug(
            self, 'Building email content from MAKO template {}'.format(
                mako_template_filepath))

        template = Template(filename=mako_template_filepath)
        # TODO - D.A. - 2014-11-06 - move this
        # Import is here for circular import problem
        import tracim.lib.helpers as helpers

        dictified_item = Context(
            CTX.EMAIL_NOTIFICATION,
            self._global_config.WEBSITE_BASE_URL).toDict(content)
        dictified_actor = Context(CTX.DEFAULT).toDict(actor)

        main_title = dictified_item.label
        content_intro = ''
        content_text = ''
        call_to_action_text = ''

        action = content.get_last_action().id
        if ActionDescription.COMMENT == action:
            content_intro = l_(
                '<span id="content-intro-username">{}</span> added a comment:'
            ).format(actor.display_name)
            content_text = content.description
            call_to_action_text = l_('Answer')

        elif ActionDescription.CREATION == action:

            # Default values (if not overriden)
            content_text = content.description
            call_to_action_text = l_('View online')

            if ContentType.Thread == content.type:
                call_to_action_text = l_('Answer')
                content_intro = l_(
                    '<span id="content-intro-username">{}</span> started a thread entitled:'
                ).format(actor.display_name)
                content_text = '<p id="content-body-intro">{}</p>'.format(content.label) + \
                               content.get_last_comment_from(actor).description

            elif ContentType.File == content.type:
                content_intro = l_(
                    '<span id="content-intro-username">{}</span> added a file entitled:'
                ).format(actor.display_name)
                if content.description:
                    content_text = content.description
                else:
                    content_text = '<span id="content-body-only-title">{}</span>'.format(
                        content.label)

            elif ContentType.Page == content.type:
                content_intro = l_(
                    '<span id="content-intro-username">{}</span> added a page entitled:'
                ).format(actor.display_name)
                content_text = '<span id="content-body-only-title">{}</span>'.format(
                    content.label)

        elif ActionDescription.REVISION == action:
            content_text = content.description
            call_to_action_text = l_('View online')

            if ContentType.File == content.type:
                content_intro = l_(
                    '<span id="content-intro-username">{}</span> uploaded a new revision.'
                ).format(actor.display_name)
                content_text = ''

            elif ContentType.Page == content.type:
                content_intro = l_(
                    '<span id="content-intro-username">{}</span> updated this page.'
                ).format(actor.display_name)
                previous_revision = content.get_previous_revision()
                title_diff = ''
                if previous_revision.label != content.label:
                    title_diff = htmldiff(previous_revision.label,
                                          content.label)
                content_text = str(l_('<p id="content-body-intro">Here is an overview of the changes:</p>'))+ \
                    title_diff + \
                    htmldiff(previous_revision.description, content.description)

            elif ContentType.Thread == content.type:
                content_intro = l_(
                    '<span id="content-intro-username">{}</span> updated the thread description.'
                ).format(actor.display_name)
                previous_revision = content.get_previous_revision()
                title_diff = ''
                if previous_revision.label != content.label:
                    title_diff = htmldiff(previous_revision.label,
                                          content.label)
                content_text = str(l_('<p id="content-body-intro">Here is an overview of the changes:</p>'))+ \
                    title_diff + \
                    htmldiff(previous_revision.description, content.description)

            # elif ContentType.Thread == content.type:
            #     content_intro = l_('<span id="content-intro-username">{}</span> updated this page.').format(actor.display_name)
            #     previous_revision = content.get_previous_revision()
            #     content_text = l_('<p id="content-body-intro">Here is an overview of the changes:</p>')+ \
            #         htmldiff(previous_revision.description, content.description)

        elif ActionDescription.EDITION == action:
            call_to_action_text = l_('View online')

            if ContentType.File == content.type:
                content_intro = l_(
                    '<span id="content-intro-username">{}</span> updated the file description.'
                ).format(actor.display_name)
                content_text = '<p id="content-body-intro">{}</p>'.format(content.get_label()) + \
                    content.description

        elif ActionDescription.STATUS_UPDATE == action:
            call_to_action_text = l_('View online')
            intro_user_msg = l_('<span id="content-intro-username">{}</span> '
                                'updated the following status:')
            content_intro = intro_user_msg.format(actor.display_name)
            intro_body_msg = '<p id="content-body-intro">{}: {}</p>'
            content_text = intro_body_msg.format(
                content.get_label(),
                content.get_status().label,
            )

        if '' == content_intro and 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 ValueError('Unexpected empty notification')

        # Import done here because cyclic import
        from tracim.config.app_cfg import CFG
        body_content = template.render(
            base_url=self._global_config.WEBSITE_BASE_URL,
            _=l_,
            h=helpers,
            user_display_name=role.user.display_name,
            user_role_label=role.role_as_label(),
            workspace_label=role.workspace.label,
            content_intro=content_intro,
            content_text=content_text,
            main_title=main_title,
            call_to_action_text=call_to_action_text,
            result=DictLikeClass(item=dictified_item, actor=dictified_actor),
            CFG=CFG.get_instance(),
        )

        return body_content
Exemplo n.º 9
0
    def _build_email_body(self, mako_template_filepath: str, role: UserRoleInWorkspace, content: Content, actor: User) -> str:
        """
        Build an email body and return it as a string
        :param mako_template_filepath: the absolute path to the mako template to be used for email body building
        :param role: the role related to user to whom the email must be sent. The role is required (and not the user only) in order to show in the mail why the user receive the notification
        :param content: the content item related to the notification
        :param actor: the user at the origin of the action / notification (for example the one who wrote a comment
        :param config: the global configuration
        :return: the built email body as string. In case of multipart email, this method must be called one time for text and one time for html
        """
        logger.debug(self, 'Building email content from MAKO template {}'.format(mako_template_filepath))

        template = Template(filename=mako_template_filepath)
        # TODO - D.A. - 2014-11-06 - move this
        # Import is here for circular import problem
        import tracim.lib.helpers as helpers

        dictified_item = Context(CTX.EMAIL_NOTIFICATION, self._global_config.WEBSITE_BASE_URL).toDict(content)
        dictified_actor = Context(CTX.DEFAULT).toDict(actor)

        main_title = dictified_item.label
        content_intro = ''
        content_text = ''
        call_to_action_text = ''

        action = content.get_last_action().id
        if ActionDescription.COMMENT == action:
            content_intro = l_('<span id="content-intro-username">{}</span> added a comment:').format(actor.display_name)
            content_text = content.description
            call_to_action_text = l_('Answer')

        elif ActionDescription.CREATION == action:

            # Default values (if not overriden)
            content_text = content.description
            call_to_action_text = l_('View online')

            if ContentType.Thread == content.type:
                call_to_action_text = l_('Answer')
                content_intro = l_('<span id="content-intro-username">{}</span> started a thread entitled:').format(actor.display_name)
                content_text = '<p id="content-body-intro">{}</p>'.format(content.label) + \
                               content.get_last_comment_from(actor).description

            elif ContentType.File == content.type:
                content_intro = l_('<span id="content-intro-username">{}</span> added a file entitled:').format(actor.display_name)
                if content.description:
                    content_text = content.description
                else:
                    content_text = '<span id="content-body-only-title">{}</span>'.format(content.label)

            elif ContentType.Page == content.type:
                content_intro = l_('<span id="content-intro-username">{}</span> added a page entitled:').format(actor.display_name)
                content_text = '<span id="content-body-only-title">{}</span>'.format(content.label)

        elif ActionDescription.REVISION == action:
            content_text = content.description
            call_to_action_text = l_('View online')

            if ContentType.File == content.type:
                content_intro = l_('<span id="content-intro-username">{}</span> uploaded a new revision.').format(actor.display_name)
                content_text = ''

            elif ContentType.Page == content.type:
                content_intro = l_('<span id="content-intro-username">{}</span> updated this page.').format(actor.display_name)
                previous_revision = content.get_previous_revision()
                title_diff = ''
                if previous_revision.label != content.label:
                    title_diff = htmldiff(previous_revision.label, content.label)
                content_text = str(l_('<p id="content-body-intro">Here is an overview of the changes:</p>'))+ \
                    title_diff + \
                    htmldiff(previous_revision.description, content.description)

            elif ContentType.Thread == content.type:
                content_intro = l_('<span id="content-intro-username">{}</span> updated the thread description.').format(actor.display_name)
                previous_revision = content.get_previous_revision()
                title_diff = ''
                if previous_revision.label != content.label:
                    title_diff = htmldiff(previous_revision.label, content.label)
                content_text = str(l_('<p id="content-body-intro">Here is an overview of the changes:</p>'))+ \
                    title_diff + \
                    htmldiff(previous_revision.description, content.description)

            # elif ContentType.Thread == content.type:
            #     content_intro = l_('<span id="content-intro-username">{}</span> updated this page.').format(actor.display_name)
            #     previous_revision = content.get_previous_revision()
            #     content_text = l_('<p id="content-body-intro">Here is an overview of the changes:</p>')+ \
            #         htmldiff(previous_revision.description, content.description)

        elif ActionDescription.EDITION == action:
            call_to_action_text = l_('View online')

            if ContentType.File == content.type:
                content_intro = l_('<span id="content-intro-username">{}</span> updated the file description.').format(actor.display_name)
                content_text = '<p id="content-body-intro">{}</p>'.format(content.get_label()) + \
                    content.description

        elif ActionDescription.STATUS_UPDATE == action:
            call_to_action_text = l_('View online')
            intro_user_msg = l_(
                '<span id="content-intro-username">{}</span> '
                'updated the following status:'
            )
            content_intro = intro_user_msg.format(actor.display_name)
            intro_body_msg = '<p id="content-body-intro">{}: {}</p>'
            content_text = intro_body_msg.format(
                content.get_label(),
                content.get_status().label,
            )

        if '' == content_intro and 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 ValueError('Unexpected empty notification')

        # Import done here because cyclic import
        from tracim.config.app_cfg import CFG
        body_content = template.render(
            base_url=self._global_config.WEBSITE_BASE_URL,
            _=l_,
            h=helpers,
            user_display_name=role.user.display_name,
            user_role_label=role.role_as_label(),
            workspace_label=role.workspace.label,
            content_intro=content_intro,
            content_text=content_text,
            main_title=main_title,
            call_to_action_text=call_to_action_text,
            result = DictLikeClass(item=dictified_item, actor=dictified_actor),
            CFG=CFG.get_instance(),
        )

        return body_content
Exemplo n.º 10
0
    def notify_content_update(self, event_actor_id: int,
                              event_content_id: int):
        """
        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.lib.content import ContentApi

        user = UserApi(None).get_one(event_actor_id)
        logger.debug(self, 'Content: {}'.format(event_content_id))

        content = ContentApi(
            user, show_archived=True, show_deleted=True
        ).get_one(
            event_content_id, ContentType.Any
        )  # TODO - use a system user instead of the user that has triggered the event
        main_content = content.parent if content.type == ContentType.Comment else content
        notifiable_roles = WorkspaceApi(user).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, 'Sending asynchronous emails to {} user(s)'.format(
                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
        async_email_sender = EmailSender(
            self._smtp_config,
            self._global_config.EMAIL_NOTIFICATION_ACTIVATED)

        for role in notifiable_roles:
            logger.info(self, 'Sending email to {}'.format(role.user.email))
            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._global_config.EMAIL_NOTIFICATION_REPLY_TO_EMAIL.replace(  # nopep8
                '{content_id}', str(content.content_id))

            reference_addr = self._global_config.EMAIL_NOTIFICATION_REFERENCES_EMAIL.replace(  #nopep8
                '{content_id}', str(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
            #
            subject = self._global_config.EMAIL_NOTIFICATION_CONTENT_UPDATE_SUBJECT
            subject = subject.replace(
                EST.WEBSITE_TITLE, self._global_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,
                main_content.get_status().label.__str__())
            reply_to_label = l_(
                '{username} & all members of {workspace}').format(
                    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))
            body_text = self._build_email_body(
                self._global_config.
                EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_TEXT, role, content,
                user)

            body_html = self._build_email_body(
                self._global_config.
                EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_HTML, role, content,
                user)

            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)

            self.log_notification(
                action='CREATED',
                recipient=message['To'],
                subject=message['Subject'],
            )
            send_email_through(async_email_sender.send_mail, message)
Exemplo n.º 11
0
    )


environment_loaded.register(lambda: start_daemons(daemons))
environment_loaded.register(lambda: configure_depot())

interrupt_manager = InterruptManager(os.getpid(), daemons_manager=daemons)

#######
#
# INFO - D.A. - 2014-10-31
# fake strings allowing to translate resetpassword tgapp.
# TODO - Integrate these translations into tgapp-resetpassword
#

l_('New password')
l_('Confirm new password')
l_('Save new password')
l_('Email address')
l_('Send Request')

l_('Password reset request')

l_('Password reset request sent')
l_('Invalid password reset request')
l_('Password reset request timed out')
l_('Invalid password reset request')
l_('Password changed successfully')

l_('''
We've received a request to reset the password for this account.
Exemplo n.º 12
0
from tg import abort
from tg import request
from tg import tmpl_context
from tracim.lib.utils import lazy_ugettext as l_
from tg.i18n import ugettext as _
from tg.predicates import Predicate

from tracim.model.data import ContentType
from tracim.lib.base import logger
from tracim.lib.content import ContentApi

from tracim.model.data import UserRoleInWorkspace


FORBIDDEN_MSG = l_('You are not authorized to access this resource')

class WorkspaceRelatedPredicate(Predicate):
    def __init__(self, **kwargs):
        super(WorkspaceRelatedPredicate, self).__init__(**kwargs)
        self.message = FORBIDDEN_MSG

    def minimal_role_level(self) -> int:
        """
        This method must be implemented in child classes. It defines the role of the user in the given workspace
        :return: required level associated to the predicate
        """
        raise NotImplementedError


    def evaluate(self, environ, credentials):