def render_notification(
        package_id: str,
        subscription: DocumentNotificationSubscription,
        data: DocumentNotificationSource,
        log_routine: Optional[Callable[[str], None]] = None
    ) -> Optional[RenderedNotification]:
        if package_id in NotificationRenderer.package_already_sent_user_ids:
            already_sent_user_ids = NotificationRenderer.package_already_sent_user_ids[
                package_id]
        else:
            already_sent_user_ids = set()
            NotificationRenderer.package_already_sent_user_ids[
                package_id] = already_sent_user_ids

        document_type = data.document.document_type
        event_info = subscription.get_event_info()
        if not event_info:
            return None

        NotificationRenderer.get_document_fields(data, document_type,
                                                 subscription, data.changes)
        doc_field_values = data.field_values
        if not doc_field_values:
            return None
        display_fields = set(doc_field_values.keys())

        recipients = subscription.resolve_recipients(data.field_values)
        recipients = {
            r
            for r in recipients if r.id not in already_sent_user_ids
        } if recipients else None
        if not recipients:
            return None

        changes_filtered = dict()
        if data.changes:
            for code, old_new in data.changes.items():
                if code in FIELD_CODES_HIDE_BY_DEFAULT:
                    continue
                if old_new[0] == old_new[1] or data.field_values.get(
                        code) == old_new[0]:
                    continue
                changes_filtered[code] = old_new
        changes = changes_filtered

        display_fields.update(changes.keys())

        template_context = {
            'app_url':
            root_url(),
            'doc_url':
            doc_editor_url(document_type.code, data.document.project_id,
                           data.document.pk),
            'event_code':
            event_info.code,
            'event_title':
            event_info.title,
            'event_initiator':
            data.changed_by_user,
            'document':
            data.field_values,
            'fields': [{
                'code': h.field_code,
                'title': h.field_title,
                'type': h.field_type,
                'value': data.field_values.get(h.field_code),
                'changed': h.field_code in changes,
                'changes': changes.get(h.field_code),
            } for h in data.field_handlers if h.field_code in display_fields],
            'changes':
            changes,
            'changed_by_user':
            data.changed_by_user,
            'logo_url':
            'images/logo.png'
        }  # type: Dict[str, Any]

        subject_template = subscription.subject or event_info.default_subject
        header_template = subscription.header or event_info.default_header

        subject = Template(subject_template).render(template_context)
        header = Template(header_template).render(template_context)

        template_context.update({'subject': subject, 'header': header})

        html = None

        html_template = get_notification_template_resource(
            os.path.join(subscription.template_name, 'template.html'))
        if html_template:
            template_src = html_template.decode('utf-8')
            template_obj = Template(template_src)
            html = template_obj.render(template_context)

        txt_template_name = os.path.join(subscription.template_name,
                                         'template.txt')
        txt_template = get_notification_template_resource(txt_template_name)
        if not txt_template:
            raise RuntimeError(f'Txt template not found: {txt_template_name}')
        txt = Template(txt_template.decode('utf-8')).render(template_context)

        image_dir = os.path.join(subscription.template_name, 'images')

        return RenderedNotification(dst_users=recipients,
                                    subject=subject,
                                    txt=txt,
                                    html=html,
                                    image_dir=image_dir,
                                    cc=subscription.get_cc_addrs())
def render_digest(config: DocumentDigestConfig,
                  dst_user: User,
                  run_date: datetime.datetime,
                  emulate_no_docs: bool = False) -> Optional[RenderedDigest]:
    period_start = None
    period_end = None

    documents_filter = DOC_FILTERS_BY_CODE[config.documents_filter]

    period = DIGEST_PERIODS_BY_CODE[config.period] if config.period else None
    if period:
        period_start, period_end = period.prepare_period(
            config, dst_user, run_date)

    user_field_codes = list(config.user_fields.all().values_list('code',
                                                                 flat=True))
    generic_field_codes = config.generic_fields or []
    field_codes = [FIELD_CODE_DOC_ID, FIELD_CODE_PROJECT_ID, FIELD_CODE_DOC_NAME] \
                  + generic_field_codes + user_field_codes

    if emulate_no_docs:
        documents = EmptyDocumentQueryResults()
    else:
        documents = documents_filter.prepare_documents(
            document_type=config.document_type,
            user=dst_user,
            field_codes=field_codes,
            period_start=period_start,
            period_end=period_end)

    if not config.still_send_if_no_docs and (not documents
                                             or not documents.documents):
        return None

    template_context = {
        'to_user': dst_user,
        'period_aware': documents_filter.period_aware,
        'period_start': period_start,
        'period_end': period_end,
        'documents': documents,
        'document_type': config.document_type,
        'doc_url_resolver': doc_editor_url,
        'app_url': root_url()
    }  # type: Dict[str, Any]

    subject_template = config.subject or documents_filter.subject
    header_template = config.header or documents_filter.header

    subject = Template(subject_template).render(template_context)
    header = Template(header_template).render(template_context)

    no_docs_message = None

    if config.still_send_if_no_docs:
        no_docs_message_template = config.message_if_no_docs or documents_filter.message_if_no_docs
        no_docs_message = Template(no_docs_message_template).render(
            template_context)

    template_context.update({
        'subject': subject,
        'header': header,
        'no_docs_message': no_docs_message
    })

    html = None

    html_template = get_notification_template_resource(
        os.path.join(config.template_name, 'template.html'))
    if html_template:
        html = Template(html_template.decode('utf-8')).render(template_context)

    txt_template_name = os.path.join(config.template_name, 'template.txt')
    txt_template = get_notification_template_resource(txt_template_name)
    if not txt_template:
        raise RuntimeError(
            'Txt template not found: {0}'.format(txt_template_name))
    txt = Template(txt_template.decode('utf-8')).render(template_context)

    image_dir = os.path.join(config.template_name, 'images')

    return RenderedDigest(config=config,
                          dst_user=dst_user,
                          date=run_date,
                          subject=subject,
                          txt=txt,
                          html=html,
                          image_dir=image_dir)
    def render_notification_pack(
            package_ids: List[str],
            subscription: DocumentNotificationSubscription,
            data: List[DocumentNotificationSource]
    ) -> List[RenderedNotification]:
        notifications_pack = []  # type: List[RenderedNotification]
        if not data:
            return notifications_pack
        event_info = subscription.get_event_info()
        if not event_info:
            return notifications_pack

        already_sent_user_ids = set()
        for pack_id in package_ids:
            if pack_id in NotificationRenderer.package_already_sent_user_ids:
                stored = NotificationRenderer.package_already_sent_user_ids[
                    pack_id]
                if stored:
                    already_sent_user_ids.update(stored)
            else:
                NotificationRenderer.package_already_sent_user_ids[
                    pack_id] = set()

        # merge message bodies
        # all the documents have the same type
        document_type = data[0].document.document_type

        template_context = {
            'app_url': root_url(),
            'event_code': event_info.code,
            'event_title': event_info.title,
            'documents': [],
            'logo_url': 'images/logo.png'
        }

        # get recipients per message, split message packs by recipients
        msgs_by_recipients = {}  # type:[str, List[DocumentNotificationSource]]
        recipients_by_key = {}  # type:[str, List[User]]
        for msg_data in data:
            NotificationRenderer.get_document_fields(msg_data, document_type,
                                                     subscription,
                                                     msg_data.changes)
            if not msg_data.field_values:
                continue

            recipients = subscription.resolve_recipients(
                msg_data.field_values)  # type:Set[User]
            recipients = {
                r
                for r in recipients if r.id not in already_sent_user_ids
            } if recipients else None
            if not recipients:
                continue
            recp_ids = [str(r.pk) for r in recipients]
            recp_ids.sort()
            recp_key = ','.join(recp_ids)
            if recp_key in msgs_by_recipients:
                msgs_by_recipients[recp_key].append(msg_data)
            else:
                msgs_by_recipients[recp_key] = [msg_data]
                recipients_by_key[recp_key] = recipients

        for recp_key in msgs_by_recipients:
            recipients = recipients_by_key[recp_key]
            for msg_data in msgs_by_recipients[recp_key]:
                doc_field_values = msg_data.field_values
                display_fields = set(doc_field_values.keys())

                changes_filtered = dict()

                if msg_data.changes:
                    for code, old_new in msg_data.changes.items():
                        if code in FIELD_CODES_HIDE_BY_DEFAULT:
                            continue
                        if old_new[0] == old_new[
                                1] or msg_data.field_values.get(
                                    code) == old_new[0]:
                            continue
                        changes_filtered[code] = old_new
                changes = changes_filtered

                display_fields.update(changes.keys())

                document_context = {
                    'doc_url':
                    doc_editor_url(document_type.code,
                                   msg_data.document.project_id,
                                   msg_data.document.pk),
                    'event_initiator':
                    msg_data.changed_by_user,
                    'document':
                    msg_data.field_values,
                    'fields': [{
                        'code': h.field_code,
                        'title': h.field_title,
                        'type': h.field_type,
                        'value': msg_data.field_values.get(h.field_code),
                        'changed': h.field_code in changes,
                        'changes': changes.get(h.field_code),
                    } for h in msg_data.field_handlers
                               if h.field_code in display_fields],
                    'changes':
                    changes,
                    'changed_by_user':
                    msg_data.changed_by_user
                }  # type: Dict[str, Any]
                template_context['documents'].append(document_context)

            html = None
            subject_template = subscription.subject or event_info.default_bulk_subject
            header_template = subscription.header or event_info.default_bulk_header

            subject = Template(subject_template).render(template_context)
            header = Template(header_template).render(template_context)

            template_context.update({'subject': subject, 'header': header})

            html_template = get_notification_template_resource(
                os.path.join(subscription.template_name, 'template_pack.html'))
            if html_template:
                html = Template(
                    html_template.decode('utf-8')).render(template_context)

            txt_template_name = os.path.join(subscription.template_name,
                                             'template_pack.txt')
            txt_template = get_notification_template_resource(
                txt_template_name)
            if not txt_template:
                raise RuntimeError(
                    'Txt template not found: {0}'.format(txt_template_name))
            txt = Template(
                txt_template.decode('utf-8')).render(template_context)

            image_dir = os.path.join(subscription.template_name, 'images')

            notification = RenderedNotification(dst_users=recipients,
                                                subject=subject,
                                                txt=txt,
                                                html=html,
                                                image_dir=image_dir,
                                                cc=subscription.get_cc_addrs())
            notifications_pack.append(notification)
        return notifications_pack
Example #4
0
def render_notification(already_sent_user_ids: Set,
                        subscription: DocumentNotificationSubscription,
                        document: Document,
                        field_handlers: List[FieldHandler],
                        field_values: Dict[str, Any],
                        changes: Dict[str, Tuple[Any, Any]] = None,
                        changed_by_user: User = None) -> Optional[RenderedNotification]:
    document_type = document.document_type
    event_info = subscription.get_event_info()
    if not event_info:
        return None
    recipients = subscription.resolve_recipients(field_values)
    recipients = {r for r in recipients if r.id not in already_sent_user_ids} if recipients else None
    if not recipients:
        return None

    display_fields = set(subscription.generic_fields or {})
    display_fields.update(FIELD_CODES_SHOW_BY_DEFAULT_GENERIC if document_type.is_generic()
                          else FIELD_CODES_SHOW_BY_DEFAULT_NON_GENERIC)
    display_fields.update({f.code for f in subscription.user_fields.all() if f.document_type == document_type})

    changes_filtered = dict()

    if changes:
        for code, old_new in changes.items():
            if code in FIELD_CODES_HIDE_BY_DEFAULT:
                continue
            if old_new[0] == old_new[1] or field_values.get(code) == old_new[0]:
                continue
            changes_filtered[code] = old_new
    changes = changes_filtered

    display_fields.update(changes.keys())

    template_context = {
        'app_url': root_url(),
        'doc_url': doc_editor_url(document_type.code, document.project_id, document.pk),
        'event_code': event_info.code,
        'event_title': event_info.title,
        'event_initiator': changed_by_user,
        'document': field_values,
        'fields': [{
            'code': h.field_code,
            'title': h.field_title,
            'type': h.field_type,
            'value': field_values.get(h.field_code),
            'changed': h.field_code in changes,
            'changes': changes.get(h.field_code),
        } for h in field_handlers if h.field_code in display_fields],
        'changes': changes,
        'changed_by_user': changed_by_user
    }  # type: Dict[str, Any]

    subject_template = subscription.subject or event_info.default_subject
    header_template = subscription.header or event_info.default_subject

    subject = Template(subject_template).render(template_context)
    header = Template(header_template).render(template_context)

    template_context.update({
        'subject': subject,
        'header': header
    })

    html = None

    html_template = get_notification_template_resource(os.path.join(subscription.template_name, 'template.html'))
    if html_template:
        html = Template(html_template.decode('utf-8')).render(template_context)

    txt_template_name = os.path.join(subscription.template_name, 'template.txt')
    txt_template = get_notification_template_resource(txt_template_name)
    if not txt_template:
        raise RuntimeError('Txt template not found: {0}'.format(txt_template_name))
    txt = Template(txt_template.decode('utf-8')).render(template_context)

    image_dir = os.path.join(subscription.template_name, 'images')

    return RenderedNotification(dst_users=recipients,
                                subject=subject,
                                txt=txt,
                                html=html,
                                image_dir=image_dir,
                                cc=subscription.get_cc_addrs())