Beispiel #1
0
    def get_document_fields(
            data: DocumentNotificationSource, document_type: DocumentType,
            subscription: DocumentNotificationSubscription) -> None:
        """
        Get document field values - those stored in Document object itself
        and those listed in subscription. Fills data.field_values dictionary.

        Make document fields codes follow RawDB "pattern": i.e.
        "document_name" instead of "name" or "assignee_name" instead of "assignee.name".
        We rename field codes because existing templates reference these "RawDB codes"
        :param data: notification's data
        :param document_type: DocumentType for associated document
        :param subscription: DocumentNotificationSubscription (refers to extra fields to obtain)
        """
        repo = DocumentFieldRepository()
        fields_to_get = \
            {f.code for f in subscription.user_fields.all() if f.document_type == document_type} if subscription \
            else set()
        fields_to_get.update(ALL_DOCUMENT_FIELD_CODES)
        doc_field_values = repo.get_document_own_and_field_values(
            data.document, fields_to_get)
        doc_field_values.update(data.field_values)
        # make field codes "RawDB style"
        for key in doc_field_values:
            new_key = DOC_FIELD_TO_CACHE_FIELD.get(key) or key
            if new_key not in data.field_values:
                data.field_values[new_key] = doc_field_values[key]
    def get_document_fields(data: DocumentNotificationSource,
                            document_type: DocumentType,
                            subscription: DocumentNotificationSubscription,
                            updated_values: Optional[Dict[str, Any]]) -> None:
        """
        Get document field values - those stored in Document object itself
        and those listed in subscription. Fills data.field_values dictionary.

        Make document fields codes follow RawDB "pattern": i.e.
        "document_name" instead of "name" or "assignee_name" instead of "assignee.name".
        We rename field codes because existing templates reference these "RawDB codes"
        :param data: notification's data
        :param document_type: DocumentType for associated document
        :param subscription: DocumentNotificationSubscription (refers to extra fields to obtain)
        :param updated_values: values that actually changed
        """
        updated_values = updated_values or {}
        repo = DocumentFieldRepository()
        fields_to_get = set()
        if subscription.user_fields:
            fields_to_get = {
                f.code
                for f in subscription.user_fields.all()
                if f.document_type == document_type
            }
        if not fields_to_get:
            doc_user_field_dict = repo.get_document_field_code_by_id(
                document_type.pk)
            if updated_values:
                fields_to_get = {
                    doc_user_field_dict[f]
                    for f in doc_user_field_dict
                    if doc_user_field_dict[f] in updated_values
                }
        if subscription.event == DocumentAssignedEvent.code:
            fields_to_get.update({
                DOCUMENT_FIELD_CODE_ASSIGNEE, DOCUMENT_FIELD_CODE_ASSIGNEE_ID,
                DOCUMENT_FIELD_CODE_ASSIGNEE_NAME
            })
        if subscription.event == DocumentLoadedEvent.code:
            obligatory_fields = {
                DOCUMENT_FIELD_CODE_NAME, DOCUMENT_FIELD_CODE_PROJECT,
                DOCUMENT_FIELD_CODE_PROJECT_NAME
            }
            updated_values.update({v: None for v in obligatory_fields})
            fields_to_get.update(obligatory_fields)

        # updated field is not chosen among fields to show
        if not any(code in fields_to_get for code in updated_values):
            data.field_values = {}
            return

        if subscription.generic_fields:
            fields_to_get.update(set(subscription.generic_fields))
            fields_to_get.update(NotificationRenderer.default_generic_fields)
        else:
            fields_to_get.update(NotificationRenderer.default_generic_fields)
        doc_field_values = repo.get_document_own_and_field_values(
            data.document, fields_to_get)
        doc_field_values.update(data.field_values)
        doc_field_values = {
            f: doc_field_values[f]
            for f in doc_field_values if f in fields_to_get
        }
        data.field_values = {
            f: data.field_values[f]
            for f in data.field_values if f in fields_to_get
        }

        # make field codes "RawDB style"
        for key in doc_field_values:
            new_key = DOC_FIELD_TO_CACHE_FIELD.get(key) or key
            if new_key not in data.field_values:
                data.field_values[new_key] = doc_field_values[key]
    def send_notifications_packet(ntfs: List[DocumentNotification],
                                  event: str,
                                  task: BaseTask):
        documents_data = list(Document.all_objects.filter(
            pk__in={d.document_id for d in ntfs}))  # type: List[Document]
        doc_type_by_id = {dt.document_type.pk:dt.document_type for dt in documents_data}
        doc_types = [doc_type_by_id[pk] for pk in doc_type_by_id]

        doc_by_id = {}  # type: Dict[int, Document]
        for doc in documents_data:
            doc_by_id[doc.pk] = doc

        users = User.objects.filter(pk__in={d.changed_by_user_id for d in ntfs})
        user_by_id = {u.pk: u for u in users}

        handlers_by_doctype = {d: build_field_handlers(d, include_annotation_fields=False)
                               for d in doc_types}  # type:Dict[str, RawdbFieldHandler]

        log = CeleryTaskLogger(task)

        # { (doc_type, event,) : [notification0, notification1, ...], ... }
        messages_by_subscr_key = {}  # type: Dict[Tuple[str, str], List[DocumentNotification]]
        # { (doc_type, event,) : [DocumentNotificationSubscription0, ... ], ... }
        subscr_by_key = {}  # type: Dict[Tuple[str, str], List[DocumentNotificationSubscription]]

        for ntf in ntfs:
            if ntf.document_id not in doc_by_id:
                continue
            document = doc_by_id[ntf.document_id]
            key = (document.document_type, ntf.event,)
            if key in messages_by_subscr_key:
                messages_by_subscr_key[key].append(ntf)
            else:
                subscriptions = DocumentNotificationSubscription.objects \
                    .filter(enabled=True,
                            document_type=document.document_type,
                            event=event,
                            recipients__isnull=False) \
                    .select_related('specified_user', 'specified_role') \
                    .prefetch_related(Prefetch('user_fields',
                                               queryset=DocumentField.objects.all().order_by('order')))
                subscr_by_key[key] = subscriptions
                messages_by_subscr_key[key] = [ntf]

        notifications_to_send = []  # type: List[RenderedNotification]

        for key in messages_by_subscr_key:
            messages = messages_by_subscr_key[key]
            subscriptions = subscr_by_key[key]
            for sub in subscriptions:
                for msg_pack in chunks(messages, sub.max_stack):
                    # render pack of notifications or just one notification
                    if len(msg_pack) < 2:
                        # render single notification
                        if msg_pack[0].document_id not in doc_by_id or \
                                not doc_by_id[msg_pack[0].document_id]:
                            raise Exception(f'Error in send_notifications_packet(1): doc '
                                            f'with id={msg_pack[0].document_id} was not obtained')
                        document = doc_by_id[msg_pack[0].document_id]
                        handlers = handlers_by_doctype[document.document_type]
                        user = user_by_id[msg_pack[0].changed_by_user_id]

                        try:
                            notification = NotificationRenderer.render_notification(
                                msg_pack[0].package_id,
                                sub,
                                DocumentNotificationSource(
                                    document=document,
                                    field_handlers=handlers,
                                    field_values=msg_pack[0].field_values,
                                    changes=msg_pack[0].changes,
                                    changed_by_user=user))
                            if notification:
                                notifications_to_send.append(notification)
                        except Exception as e:
                            log.error(f'Error in send_notifications_packet(1), '
                                      f'sending render_notification()', exc_info=e)
                    else:
                        not_sources = []  # List[DocumentNotificationSource
                        # render pack of notifications in a single message
                        for msg in msg_pack:
                            if msg.document_id not in doc_by_id or \
                                    not doc_by_id[msg.document_id]:
                                raise Exception(f'Error in send_notifications_packet({len(msg_pack)}: doc '
                                                f'with id={msg.document_id} was not obtained')

                            document = doc_by_id[msg.document_id]
                            handlers = handlers_by_doctype[document.document_type]
                            user = user_by_id[msg.changed_by_user_id]
                            not_src = DocumentNotificationSource(
                                document=document,
                                field_handlers=handlers,
                                field_values=msg.field_values,
                                changes=msg.changes,
                                changed_by_user=user)
                            not_sources.append(not_src)
                        try:
                            notifications = NotificationRenderer.render_notification_pack(
                                [m.package_id for m in msg_pack],
                                sub, not_sources)
                            notifications_to_send += notifications
                        except Exception as e:
                            log.error(f'Error in send_notifications_packet(), '
                                      f'sending render_notification_pack()', exc_info=e)

        log.info(f'notification.send({len(notifications_to_send)})')
        for notification in notifications_to_send:
            notification.send(log=log)
    def get(self, request, subscription_id, content_format, **_kwargs):
        send_email = as_bool(request.GET, self.PARAM_SEND, False)

        subscription = DocumentNotificationSubscription.objects.get(
            pk=subscription_id)

        document_type = subscription.document_type

        document_id = as_int(request.GET, self.PARAM_DOCUMENT, None)
        if document_id:
            document = Document.objects.filter(document_type=document_type,
                                               pk=document_id).first()
            if not document:
                return HttpResponseBadRequest(
                    'Document with id = {0} not found or has wrong type.'.
                    format(document_id))
        else:
            document = Document.objects.filter(
                document_type=document_type).first()
            if not document:
                return HttpResponseBadRequest(
                    'Document id not provided and '
                    'there are no example documents of type {0}.'.format(
                        document_type.code))

        document_id = document.pk
        field_handlers = build_field_handlers(document_type,
                                              include_annotation_fields=False)
        field_values = get_document_field_values(document_type,
                                                 document_id,
                                                 handlers=field_handlers)

        example_changes = dict()
        if subscription.event in {
                DocumentAssignedEvent.code, DocumentChangedEvent.code
        } and field_values:
            for h in field_handlers:
                if random.random() > 0.3:
                    continue
                field = DocumentField.objects.filter(code=h.field_code).first()
                if not field:
                    continue
                typed_field = TypedField.by(field)
                example_value = typed_field.example_python_value()
                example_changes[h.field_code] = (example_value,
                                                 field_values.get(
                                                     h.field_code))

        try:
            notification = NotificationRenderer.render_notification(
                uuid.uuid4().hex, subscription,
                DocumentNotificationSource(document=document,
                                           field_handlers=field_handlers,
                                           field_values=field_values,
                                           changes=example_changes,
                                           changed_by_user=request.user))
        except Exception as e:
            return HttpResponse(render_error(
                'Exception caught while trying to render notification', e),
                                status=500,
                                content_type='text/plain')
        if not notification:
            return HttpResponse('Notification contains no data.', status=200)

        if content_format == self.FORMAT_HTML:
            content = notification.html
            content_type = 'text/html'
        else:
            content = notification.txt
            content_type = 'text/plain'

        if send_email:
            log = ErrorCollectingLogger()
            notification.send(log=log)
            error = log.get_error()
            if error:
                return HttpResponseServerError(content=error,
                                               content_type='application/json')

        return HttpResponse(content=content,
                            content_type=content_type,
                            status=200)