Пример #1
0
    def send(self, str):
        """Send `str' to the server."""
        if self.debuglevel > 0: print >> stderr, 'send:', repr(str)
        if self.sock:
            total = len(
                str)  # the total length of the str(the email) to be sent
            sent = 0  # the size of the sent data (c)
            buff = 2048  # buff size (c)
            try:
                while (1):
                    self._updateProgress(
                        total, sent,
                        "Transferring...... (%d/%d)" % (sent, total))
                    #self.ui.updateStatus("Transferring...... (%d/%d)"%(sent,total))
                    begin, end = sent, sent + buff
                    data = str[begin:end]
                    len_of_data = len(data)
                    if len_of_data > 0:
                        self.sock.sendall(data)
                        sent += len_of_data
                    else:
                        break

            except socket.error:
                self.close()
                raise SMTPServerDisconnected('Server not connected')
        else:
            raise SMTPServerDisconnected('please run connect() first')
class SMTPCheckTest(TestCase):
    'Collection of tests the `smtp_check` method.'

    # All the possible ways to fail we want to test, listed as tuples
    # containing (command, reply, expected exception).
    failures = [
        # Timeout on connection
        (None, timeout(), SMTPTemporaryError),
        # Connection unexpectedly closed during any stage
        (None, SMTPServerDisconnected('Test'), SMTPTemporaryError),
        ('EHLO', SMTPServerDisconnected('Test'), SMTPTemporaryError),
        ('HELO', SMTPServerDisconnected('Test'), SMTPTemporaryError),
        ('MAIL', SMTPServerDisconnected('Test'), SMTPTemporaryError),
        ('RCPT', SMTPServerDisconnected('Test'), SMTPTemporaryError),
        # Temporary error codes
        (None, (421, b'Connect failed'), SMTPTemporaryError),
        ('HELO', (421, b'HELO failed'), SMTPTemporaryError),
        ('MAIL', (451, b'MAIL FROM failed'), SMTPTemporaryError),
        ('RCPT', (451, b'RCPT TO failed'), SMTPTemporaryError),
        # Permanent error codes
        (None, (554, b'Connect failed'), SMTPCommunicationError),
        ('HELO', (504, b'HELO failed'), SMTPCommunicationError),
        ('MAIL', (550, b'MAIL FROM failed'), SMTPCommunicationError),
        ('RCPT', (550, b'RCPT TO failed'), AddressNotDeliverableError),
    ]

    @patch(target='validate_email.smtp_check._SMTPChecker', new=SMTPMock)
    def test_smtp_success(self):
        'Succeeds on successful SMTP conversation'
        self.assertTrue(
            smtp_check(
                email_address=EmailAddress('*****@*****.**'),
                mx_records=['smtp.example.com'],
            )
        )

    def _test_one_smtp_failure(self, cmd, reply, exception):
        with patch.dict(in_dict=SMTPMock.reply, values={cmd: reply}):
            with self.assertRaises(exception) as context:
                smtp_check(
                    email_address=EmailAddress('*****@*****.**'),
                    mx_records=['smtp.example.com'],
                )
            if isinstance(reply, tuple):
                error_messages = context.exception.error_messages
                error_info = error_messages['smtp.example.com']
                self.assertEqual(error_info.command[:4].upper(), cmd or 'CONN')
                self.assertEqual(error_info.code, reply[0])
                self.assertEqual(error_info.text, reply[1].decode())

    @patch(target='validate_email.smtp_check._SMTPChecker', new=SMTPMock)
    def test_smtp_failure(self):
        'Fails on unsuccessful SMTP conversation.'
        for cmd, reply, exception in self.failures:
            with self.subTest(cmd=cmd, reply=reply):
                self._test_one_smtp_failure(cmd, reply, exception)
Пример #3
0
    def getreply(self):
        """Get a reply from the server.

        Returns a tuple consisting of:

          - server response code (e.g. '250', or such, if all goes well)
            Note: returns -1 if it can't read response code.

          - server response string corresponding to response code (multiline
            responses are converted to a single, multiline string).

        Raises SMTPServerDisconnected if end-of-file is reached.
        """
        resp = []
        if self.file is None:
            self.file = self.sock.makefile('rb')
        while 1:
            try:
                line = self.file.readline(_MAXLINE + 1)
            except OSError as e:
                self.close()
                raise SMTPServerDisconnected(
                    "Connection unexpectedly closed: " + str(e))
            if not line:
                self.close()
                raise SMTPServerDisconnected("Connection unexpectedly closed")
            if self.debuglevel > 0:
                self._print_debug('reply:', repr(line))
            if len(line) > _MAXLINE:
                self.close()
                raise SMTPResponseException(500, "Line too long.")
            resp.append(line[4:].strip(b' \t\r\n'))
            code = line[:3]
            # Check that the error code is syntactically correct.
            # Don't attempt to read a continuation line if it is broken.
            try:
                errcode = int(code)
            except ValueError:
                errcode = -1
                break
            # Check if multiline response.
            if line[3:4] != b"-":
                break

        errmsg = b"\n".join(resp)
        if self.debuglevel > 0:
            self._print_debug('reply: retcode (%s); Msg: %r' %
                              (errcode, errmsg))
        return errcode, errmsg
Пример #4
0
 def send(self, s):
     """Send `s' to the server."""
     if self.debuglevel > 0:
         self._print_debug('send:', repr(s))
     if hasattr(self, 'sock') and self.sock:
         if isinstance(s, str):
             # send is used by the 'data' command, where command_encoding
             # should not be used, but 'data' needs to convert the string to
             # binary itself anyway, so that's not a problem.
             s = s.encode(self.command_encoding)
         try:
             self.sock.sendall(s)
         except OSError:
             self.close()
             raise SMTPServerDisconnected('Server not connected')
     else:
         raise SMTPServerDisconnected('please run connect() first')
Пример #5
0
    def test_server_disconnected(self):
        # to handle this kind of error
        # http://docs.python.org/2.7/library/smtplib.html#smtplib.SMTPServerDisconnected

        with patch("django.core.mail.EmailMultiAlternatives.send") as send_mail:
            send_mail.side_effect = SMTPServerDisconnected()
            result_of_sending, fatal_error = self.channel.send(self.outbound_message1)
            self.assertFalse(result_of_sending)
            self.assertFalse(fatal_error)
Пример #6
0
    def test_send_mime_partial_failure(self, mock_smtp, mock_smtp_ssl):
        final_mock = mock.Mock()
        side_effects = [SMTPServerDisconnected(), SMTPServerDisconnected(), final_mock]
        mock_smtp.side_effect = side_effects
        msg = MIMEMultipart()

        utils.email.send_mime_email('from', 'to', msg, dryrun=False)

        mock_smtp.assert_any_call(
            host=conf.get('smtp', 'SMTP_HOST'),
            port=conf.getint('smtp', 'SMTP_PORT'),
            timeout=conf.getint('smtp', 'SMTP_TIMEOUT'),
        )
        assert mock_smtp.call_count == side_effects.index(final_mock) + 1
        assert not mock_smtp_ssl.called
        assert final_mock.starttls.called
        final_mock.sendmail.assert_called_once_with('from', 'to', msg.as_string())
        assert final_mock.quit.called
Пример #7
0
 def _ir_mail_server_send_email(model, message, *args, **kwargs):
     if '@' not in message['To']:
         raise AssertionError(model.NO_VALID_RECIPIENT)
     if sim_error and sim_error == 'send_assert':
         raise AssertionError('AssertionError')
     elif sim_error and sim_error == 'send_disconnect':
         raise SMTPServerDisconnected('SMTPServerDisconnected')
     elif sim_error and sim_error == 'send_delivery':
         raise MailDeliveryException('MailDeliveryException')
     return message['Message-Id']
Пример #8
0
    def test_email_gets_cleared_on_failure(self):
        bn = BatchNotifier(batch_mode='all')

        bn.add_failure('Task(a=5)', 'Task', {'a': 1}, '', [])
        self.send_email.side_effect = SMTPServerDisconnected('timeout')
        self.assertRaises(SMTPServerDisconnected, bn.send_email)

        self.send_email.reset_mock()
        bn.send_email()
        self.send_email.assert_not_called()
Пример #9
0
def test_send_retries_if_mailing_fails(pyramid_mailer, celery):
    celery.request = mock.sentinel.request
    request_mailer = pyramid_mailer.get_mailer.return_value
    request_mailer.send_immediately.side_effect = SMTPServerDisconnected()

    mailer.send.retry = mock.Mock(spec_set=[])
    mailer.send(recipients=['*****@*****.**'],
                subject='My email subject',
                body='Some text body')

    assert mailer.send.retry.called
Пример #10
0
def test_send_retries_if_mailing_fails(pyramid_mailer, celery, pyramid_request):
    celery.request = pyramid_request
    request_mailer = pyramid_mailer.get_mailer.return_value
    request_mailer.send_immediately.side_effect = SMTPServerDisconnected()

    mailer.send.retry = mock.Mock(spec_set=[])
    mailer.send(  # pylint:disable=no-value-for-parameter #: bound celery task
        recipients=["*****@*****.**"],
        subject="My email subject",
        body="Some text body",
    )

    assert mailer.send.retry.called
Пример #11
0
    def ehlo(self, name=''):
        """ SMTP 'ehlo' command.
        Hostname to send for this command defaults to the FQDN of the local
        host.
        """
        self.esmtp_features = {}
        self.putcmd(self.ehlo_msg, name or self.local_hostname)
        (code, msg) = self.getreply()
        # According to RFC1869 some (badly written)
        # MTA's will disconnect on an ehlo. Toss an exception if
        # that happens -ddm
        if code == -1 and len(msg) == 0:
            self.close()
            raise SMTPServerDisconnected("Server not connected")
        self.ehlo_resp = msg
        if code != 250:
            return (code, msg)
        self.does_esmtp = 1
        #parse the ehlo response -ddm
        assert isinstance(self.ehlo_resp, bytes), repr(self.ehlo_resp)
        resp = self.ehlo_resp.decode("latin-1").split('\n')
        del resp[0]
        for each in resp:
            # To be able to communicate with as many SMTP servers as possible,
            # we have to take the old-style auth advertisement into account,
            # because:
            # 1) Else our SMTP feature parser gets confused.
            # 2) There are some servers that only advertise the auth methods we
            #    support using the old style.
            auth_match = OLDSTYLE_AUTH.match(each)
            if auth_match:
                # This doesn't remove duplicates, but that's no problem
                self.esmtp_features["auth"] = self.esmtp_features.get("auth", "") \
                        + " " + auth_match.groups(0)[0]
                continue

            # RFC 1869 requires a space between ehlo keyword and parameters.
            # It's actually stricter, in that only spaces are allowed between
            # parameters, but were not going to check for that here.  Note
            # that the space isn't present if there are no parameters.
            m = re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*) ?', each)
            if m:
                feature = m.group("feature").lower()
                params = m.string[m.end("feature"):].strip()
                if feature == "auth":
                    self.esmtp_features[feature] = self.esmtp_features.get(feature, "") \
                            + " " + params
                else:
                    self.esmtp_features[feature] = params
        return (code, msg)
Пример #12
0
    def get_response(self):
        """Get a status reponse from the server.

        Returns a tuple consisting of:

          - server response code (e.g. '250', or such, if all goes well)
            Note: returns -1 if it can't read response code.

          - server response string corresponding to response code (multiline
            responses are converted to a single, multiline string).

        Raises SMTPResponseException for codes > 500.
        """
        code = -1
        response = []
        while True:
            try:
                line = yield from self.reader.readline()
            except ConnectionResetError as exc:
                raise SMTPServerDisconnected(exc)

            if not line:
                break

            if len(line) > MAX_LINE_LENGTH:
                raise SMTPResponseException(500, "Line too long.")

            code = line[:3]
            message = line[4:]
            message = message.strip(b' \t\r\n')  # Strip newlines
            message = message.decode()  # Convert to string
            response.append(message)

            try:
                code = int(code)
            except ValueError:
                code = -1

            if line[3:4] != b"-":
                break

        message = "\n".join(response)
        if self.debug:
            logger.debug("reply: %s %s", code, message)
        if 500 <= code <= 599:
            raise SMTPResponseException(code, message)

        return code, message
Пример #13
0
    def test_send_mime_complete_failure(self, mock_smtp: mock, mock_smtp_ssl):
        mock_smtp.side_effect = SMTPServerDisconnected()
        msg = MIMEMultipart()
        with pytest.raises(SMTPServerDisconnected):
            utils.email.send_mime_email('from', 'to', msg, dryrun=False)

        mock_smtp.assert_any_call(
            host=conf.get('smtp', 'SMTP_HOST'),
            port=conf.getint('smtp', 'SMTP_PORT'),
            timeout=conf.getint('smtp', 'SMTP_TIMEOUT'),
        )
        assert mock_smtp.call_count == conf.getint('smtp', 'SMTP_RETRY_LIMIT')
        assert not mock_smtp_ssl.called
        assert not mock_smtp.return_value.starttls.called
        assert not mock_smtp.return_value.login.called
        assert not mock_smtp.return_value.sendmail.called
        assert not mock_smtp.return_value.quit.called
Пример #14
0
    def emailInvoice(self, templateHTML, to=[]):
        """
        Send the invoice via email.
        :param templateHTML: The invoice template in HTML, ready to be send.
        :param to: A list with the addresses to send the invoice.
        """
        ar = self.aq_parent
        # SMTP errors are silently ignored if server is in debug mode
        debug_mode = App.config.getConfiguration().debug_mode
        # Useful variables
        lab = ar.bika_setup.laboratory
        # Compose and send email.
        subject = t(_('Invoice')) + ' ' + ar.getInvoice().getId()
        mime_msg = MIMEMultipart('related')
        mime_msg['Subject'] = subject
        mime_msg['From'] = formataddr(
            (encode_header(lab.getName()), lab.getEmailAddress()))
        mime_msg.preamble = 'This is a multi-part MIME message.'
        msg_txt_t = MIMEText(templateHTML.encode('utf-8'), _subtype='html')
        mime_msg.attach(msg_txt_t)

        # Build the responsible's addresses
        mngrs = ar.getResponsible()
        for mngrid in mngrs['ids']:
            name = mngrs['dict'][mngrid].get('name', '')
            email = mngrs['dict'][mngrid].get('email', '')
            if (email != ''):
                to.append(formataddr((encode_header(name), email)))
        # Build the client's address
        caddress = ar.aq_parent.getEmailAddress()
        cname = ar.aq_parent.getName()
        if (caddress != ''):
            to.append(formataddr((encode_header(cname), caddress)))
        if len(to) > 0:
            # Send the emails
            mime_msg['To'] = ','.join(to)
            try:
                host = getToolByName(ar, 'MailHost')
                host.send(mime_msg.as_string(), immediate=True)
            except SMTPServerDisconnected as msg:
                pass
                if not debug_mode:
                    raise SMTPServerDisconnected(msg)
            except SMTPRecipientsRefused as msg:
                raise WorkflowException(str(msg))
Пример #15
0
    def test_disconn_err_retry(self, retry, get_conn):
        """
        Test that celery handles SMTPServerDisconnected by retrying.
        """
        get_conn.return_value.open.side_effect = SMTPServerDisconnected(425, "Disconnecting")
        test_email = {
            'action': 'Send email',
            'send_to': 'myself',
            'subject': 'test subject for myself',
            'message': 'test message for myself'
        }
        response = self.client.post(self.send_mail_url, test_email)
        self.assertEquals(json.loads(response.content), self.success_content)

        self.assertTrue(retry.called)
        (__, kwargs) = retry.call_args
        exc = kwargs['exc']
        self.assertIsInstance(exc, SMTPServerDisconnected)
    def test_disconn_err_retry(self, retry, get_conn):
        """
        Test that celery handles SMTPServerDisconnected by retrying.
        """
        get_conn.return_value.open.side_effect = SMTPServerDisconnected(
            425, "Disconnecting")
        test_email = {
            'action': 'Send email',
            'to_option': 'myself',
            'subject': 'test subject for myself',
            'message': 'test message for myself'
        }
        self.client.post(self.url, test_email)

        self.assertTrue(retry.called)
        (_, kwargs) = retry.call_args
        exc = kwargs['exc']
        self.assertTrue(type(exc) == SMTPServerDisconnected)
 def connect(self,
             host: str = 'localhost',
             port: int = 0,
             source_address: str = None) -> Tuple[int, str]:
     """
     Like `smtplib.SMTP.connect`, but raise appropriate exceptions on
     connection failure or negative SMTP server response.
     """
     self.__command = 'connect'  # Used for error messages.
     self._host = host  # Workaround: Missing in standard smtplib!
     try:
         code, message = super().connect(host=host,
                                         port=port,
                                         source_address=source_address)
     except OSError as error:
         raise SMTPServerDisconnected(str(error))
     if code >= 400:
         raise SMTPResponseException(code=code, msg=message)
     return code, message
Пример #18
0
    def test_send_mime_ssl_complete_failure(self, mock_smtp, mock_smtp_ssl):
        mock_smtp_ssl.side_effect = SMTPServerDisconnected()
        msg = MIMEMultipart()
        with conf_vars({('smtp', 'smtp_ssl'): 'True'}):
            with self.assertRaises(SMTPServerDisconnected):
                utils.email.send_mime_email('from', 'to', msg, dryrun=False)

        mock_smtp_ssl.assert_any_call(
            host=conf.get('smtp', 'SMTP_HOST'),
            port=conf.getint('smtp', 'SMTP_PORT'),
            timeout=conf.getint('smtp', 'SMTP_TIMEOUT'),
        )
        self.assertEqual(mock_smtp_ssl.call_count,
                         conf.getint('smtp', 'SMTP_RETRY_LIMIT'))
        self.assertFalse(mock_smtp.called)
        self.assertFalse(mock_smtp_ssl.return_value.starttls.called)
        self.assertFalse(mock_smtp_ssl.return_value.login.called)
        self.assertFalse(mock_smtp_ssl.return_value.sendmail.called)
        self.assertFalse(mock_smtp_ssl.return_value.quit.called)
Пример #19
0
    def ehlo(self, hostname=None):
        """Send the SMTP 'ehlo' command.
        Hostname to send for this command defaults to the FQDN of the local
        host.

        Returns a (code, message) tuple with the server response.
        """
        hostname = hostname or self.local_hostname
        code, message = yield from self.execute_command("ehlo", hostname)
        # According to RFC1869 some (badly written)
        # MTA's will disconnect on an ehlo. Toss an exception if
        # that happens -ddm
        if code == SMTP_NO_CONNECTION and len(message) == 0:
            self.close()
            raise SMTPServerDisconnected("Server not connected")
        elif code == SMTP_COMPLETED:
            self.parse_esmtp_response(code, message)

        self.last_helo_status = (code, message)
        return code, message
Пример #20
0
    def test_send_mime_custom_timeout_retrylimit(self, mock_smtp,
                                                 mock_smtp_ssl):
        mock_smtp.side_effect = SMTPServerDisconnected()
        msg = MIMEMultipart()

        custom_retry_limit = 10
        custom_timeout = 60

        with conf_vars({
            ('smtp', 'smtp_retry_limit'): str(custom_retry_limit),
            ('smtp', 'smtp_timeout'): str(custom_timeout),
        }):
            with pytest.raises(SMTPServerDisconnected):
                utils.email.send_mime_email('from', 'to', msg, dryrun=False)

        mock_smtp.assert_any_call(host=conf.get('smtp', 'SMTP_HOST'),
                                  port=conf.getint('smtp', 'SMTP_PORT'),
                                  timeout=custom_timeout)
        assert not mock_smtp_ssl.called
        assert mock_smtp.call_count == 10
Пример #21
0
 def sendEmail(self):
     added = []
     to = ''
     for analysis in self.analyses:
         department = analysis.getService().getDepartment()
         if department is None:
             continue
         department_id = department.UID()
         if department_id in added:
             continue
         added.append(department_id)
         manager = department.getManager()
         if manager is None:
             continue
         manager_id = manager.UID()
         if manager_id not in added and manager.getEmailAddress():
             added.append(manager_id)
             name = safe_unicode(manager.getFullname()).encode('utf-8')
             email = safe_unicode(manager.getEmailAddress()).encode('utf-8')
             to = '%s, %s' % (to, formataddr((encode_header(name), email)))
     html = safe_unicode(self.template()).encode('utf-8')
     lab = self.context.bika_setup.laboratory
     mime_msg = MIMEMultipart('related')
     mime_msg['Subject'] = self.title
     mime_msg['From'] = formataddr(
         (encode_header(lab.getName()),
          lab.getEmailAddress()))
     mime_msg['To'] = to
     mime_msg.preamble = 'This is a multi-part MIME message.'
     msg_txt = MIMEText(html, _subtype='html')
     mime_msg.attach(msg_txt)
     # Send the email
     try:
         host = getToolByName(self.context, 'MailHost')
         host.send(mime_msg.as_string(), immediate=True)
     except SMTPServerDisconnected as msg:
         raise SMTPServerDisconnected(msg)
     except SMTPRecipientsRefused as msg:
         raise WorkflowException(str(msg))
Пример #22
0
 def test_max_retry_after_smtp_disconnect(self):
     self._test_max_retry_limit_causes_failure(
         SMTPServerDisconnected(425, "Disconnecting"))
Пример #23
0
 def test_retry_after_smtp_disconnect(self):
     self._test_retry_after_limited_retry_error(
         SMTPServerDisconnected(425, "Disconnecting"))
Пример #24
0
    def publishFromHTML(self, aruid, results_html):
        # The AR can be published only and only if allowed
        uc = getToolByName(self.context, 'uid_catalog')
        ars = uc(UID=aruid)
        if not ars or len(ars) != 1:
            return []

        ar = ars[0].getObject()
        wf = getToolByName(ar, 'portal_workflow')
        allowed_states = ['verified', 'published']
        # Publish/Republish allowed?
        if wf.getInfoFor(ar, 'review_state') not in allowed_states:
            # Pre-publish allowed?
            if not ar.getAnalyses(review_state=allowed_states):
                return []

        # SMTP errors are silently ignored if server is in debug mode
        debug_mode = App.config.getConfiguration().debug_mode
        # PDF and HTML files are written to disk if server is in debug mode
        out_path = join(Globals.INSTANCE_HOME, 'var') if debug_mode \
            else None
        out_fn = to_utf8(ar.Title())

        if out_path:
            open(join(out_path, out_fn + ".html"), "w").write(results_html)

        # Create the pdf report (will always be attached to the AR)
        pdf_outfile = join(out_path, out_fn + ".pdf") if out_path else None
        pdf_report = createPdf(results_html, pdf_outfile)

        recipients = []
        contact = ar.getContact()
        lab = ar.bika_setup.laboratory
        if pdf_report:
            if contact:
                recipients = [{
                    'UID':
                    contact.UID(),
                    'Username':
                    to_utf8(contact.getUsername()),
                    'Fullname':
                    to_utf8(contact.getFullname()),
                    'EmailAddress':
                    to_utf8(contact.getEmailAddress()),
                    'PublicationModes':
                    contact.getPublicationPreference()
                }]
            reportid = ar.generateUniqueId('ARReport')
            report = _createObjectByType("ARReport", ar, reportid)
            report.edit(AnalysisRequest=ar.UID(),
                        Pdf=pdf_report,
                        Html=results_html,
                        Recipients=recipients)
            report.unmarkCreationFlag()
            renameAfterCreation(report)

            # Set status to prepublished/published/republished
            status = wf.getInfoFor(ar, 'review_state')
            transitions = {'verified': 'publish', 'published': 'republish'}
            transition = transitions.get(status, 'prepublish')
            try:
                wf.doActionFor(ar, transition)
            except WorkflowException:
                pass

            # compose and send email.
            # The managers of the departments for which the current AR has
            # at least one AS must receive always the pdf report by email.
            # https://github.com/bikalabs/Bika-LIMS/issues/1028
            mime_msg = MIMEMultipart('related')
            mime_msg['Subject'] = self.get_mail_subject(ar)[0]
            mime_msg['From'] = formataddr(
                (encode_header(lab.getName()), lab.getEmailAddress()))
            mime_msg.preamble = 'This is a multi-part MIME message.'
            msg_txt = MIMEText(results_html, _subtype='html')
            mime_msg.attach(msg_txt)

            to = []
            mngrs = ar.getResponsible()
            for mngrid in mngrs['ids']:
                name = mngrs['dict'][mngrid].get('name', '')
                email = mngrs['dict'][mngrid].get('email', '')
                if (email != ''):
                    to.append(formataddr((encode_header(name), email)))

            if len(to) > 0:
                # Send the email to the managers
                mime_msg['To'] = ','.join(to)
                attachPdf(mime_msg, pdf_report, out_fn)

                try:
                    host = getToolByName(ar, 'MailHost')
                    host.send(mime_msg.as_string(), immediate=True)
                except SMTPServerDisconnected as msg:
                    pass
                    if not debug_mode:
                        raise SMTPServerDisconnected(msg)
                except SMTPRecipientsRefused as msg:
                    raise WorkflowException(str(msg))

        # Send report to recipients
        recips = self.get_recipients(ar)
        for recip in recips:
            if 'email' not in recip.get('pubpref', []) \
                    or not recip.get('email', ''):
                continue

            title = encode_header(recip.get('title', ''))
            email = recip.get('email')
            formatted = formataddr((title, email))

            # Create the new mime_msg object, cause the previous one
            # has the pdf already attached
            mime_msg = MIMEMultipart('related')
            mime_msg['Subject'] = self.get_mail_subject(ar)[0]
            mime_msg['From'] = formataddr(
                (encode_header(lab.getName()), lab.getEmailAddress()))
            mime_msg.preamble = 'This is a multi-part MIME message.'
            msg_txt = MIMEText(results_html, _subtype='html')
            mime_msg.attach(msg_txt)
            mime_msg['To'] = formatted

            # Attach the pdf to the email if requested
            if pdf_report and 'pdf' in recip.get('pubpref'):
                attachPdf(mime_msg, pdf_report, out_fn)

            # For now, I will simply ignore mail send under test.
            if hasattr(self.portal, 'robotframework'):
                continue

            try:
                host = getToolByName(ar, 'MailHost')
                host.send(mime_msg.as_string(), immediate=True)
            except SMTPServerDisconnected as msg:
                if not debug_mode:
                    raise SMTPServerDisconnected(msg)
            except SMTPRecipientsRefused as msg:
                raise WorkflowException(str(msg))

        ar.setDatePublished(DateTime())

        return [ar]
Пример #25
0
    def __call__(self):

        rc = getToolByName(self.context, REFERENCE_CATALOG)
        workflow = getToolByName(self.context, 'portal_workflow')

        BatchEmail = self.context.bika_setup.getBatchEmail()

        username = self.context.portal_membership.getAuthenticatedMember().getUserName()
        self.reporter = self.user_fullname(username)
        self.reporter_email = self.user_email(username)

        # signature image
        self.reporter_signature = ""
        c = [x for x in self.bika_setup_catalog(portal_type='LabContact')
             if x.getObject().getUsername() == username]
        if c:
            sf = c[0].getObject().getSignature()
            if sf:
                self.reporter_signature = sf.absolute_url() + "/Signature"

        # lab address
        self.laboratory = laboratory = self.context.bika_setup.laboratory
        lab_address = laboratory.getPostalAddress() \
            or laboratory.getBillingAddress() \
            or laboratory.getPhysicalAddress()
        if lab_address:
            _keys = ['address', 'city', 'state', 'zip', 'country']
            _list = [lab_address.get(v) for v in _keys]
            self.lab_address = "<br/>".join(_list).replace("\n", "<br/>")
        else:
            self.lab_address = None

        # group/publish analysis requests by contact
        ARs_by_contact = {}
        for ar in self.analysis_requests:
            contact_uid = ar.getContact().UID()
            if contact_uid not in ARs_by_contact:
                ARs_by_contact[contact_uid] = []
            ARs_by_contact[contact_uid].append(ar)

        for contact_uid, ars in ARs_by_contact.items():
            ars.sort()
            self.contact = ars[0].getContact()
            self.pub_pref = self.contact.getPublicationPreference()
            batch_size = 'email' in self.pub_pref and BatchEmail or 5

            # client address
            self.client = ars[0].aq_parent
            client_address = self.client.getPostalAddress() \
                or self.contact.getBillingAddress() \
                or self.contact.getPhysicalAddress()
            if client_address:
                _keys = ['address', 'city', 'state', 'zip', 'country']
                _list = [client_address.get(v) for v in _keys]
                self.client_address = "<br/>".join(_list).replace("\n", "<br/>")
            else:
                self.client_address = None

            self.Footer = self.context.bika_setup.getResultFooter()

            # send batches of ARs to each contact
            for b in range(0, len(ars), batch_size):
                self.batch = ars[b:b+batch_size]
                self.any_accredited = False
                self.any_drymatter = False
                # get all services from all requests in this batch into a
                # dictionary:
                #   {'Point Of Capture': {'Category': [service,service,...]}}
                self.services = {}

                out_fn = "_".join([ar.Title() for ar in self.batch])

                for ar in self.batch:
                    if ar.getReportDryMatter():
                        self.any_drymatter = True
                    states = ("verified", "published")
                    for analysis in ar.getAnalyses(full_objects=True,
                                                   review_state=states):
                        service = analysis.getService()
                        poc = POINTS_OF_CAPTURE.getValue(service.getPointOfCapture())
                        cat = service.getCategoryTitle()
                        if poc not in self.services:
                            self.services[poc] = {}
                        if cat not in self.services[poc]:
                            self.services[poc][cat] = []
                        if service not in self.services[poc][cat]:
                            self.services[poc][cat].append(service)
                        if (service.getAccredited()):
                            self.any_accredited = True

                # compose and send email
                if 'email' in self.pub_pref:

                    # render template
                    ar_results = self.ar_results()

                    debug_mode = App.config.getConfiguration().debug_mode
                    if debug_mode:
                        open(join(Globals.INSTANCE_HOME,'var', out_fn + ".html"),
                             "w").write(ar_results)

                    pisa.showLogging()
                    ramdisk = StringIO()
                    pdf = pisa.CreatePDF(ar_results, ramdisk)
                    pdf_data = ramdisk.getvalue()
                    ramdisk.close()

                    if debug_mode:
                        open(join(Globals.INSTANCE_HOME,'var', out_fn + ".pdf"),
                             "wb").write(pdf_data)

                    mime_msg = MIMEMultipart('related')
                    mime_msg['Subject'] = self.get_mail_subject()
                    mime_msg['From'] = formataddr(
                        (encode_header(laboratory.getName()),
                         laboratory.getEmailAddress()))
                    mime_msg['To'] = formataddr(
                        (encode_header(self.contact.getFullname()),
                         self.contact.getEmailAddress()))
                    mime_msg.preamble = 'This is a multi-part MIME message.'
                    msg_txt = MIMEText(ar_results, _subtype='html')
                    mime_msg.attach(msg_txt)
                    if not pdf.err:
                        part = MIMEBase('application', "application/pdf")
                        part.add_header('Content-Disposition', 'attachment; filename="%s.pdf"' % out_fn)
                        part.set_payload( pdf_data )
                        Encoders.encode_base64(part)
                        mime_msg.attach(part)

                    try:
                        host = getToolByName(self.context, 'MailHost')
                        host.send(mime_msg.as_string(), immediate=True)
                    except SMTPServerDisconnected, msg:
                        if not debug_mode:
                            raise SMTPServerDisconnected(msg)
                    except SMTPRecipientsRefused, msg:
                        raise WorkflowException(str(msg))

                    if self.action == 'publish':
                        for ar in self.batch:
                            try:
                                workflow.doActionFor(ar, 'publish')
                            except WorkflowException:
                                pass

##                    if not pdf.err:
##                        setheader = self.request.RESPONSE.setHeader
##                        setheader('Content-Type', 'application/pdf')
##                        setheader("Content-Disposition", "attachment;filename=\"%s.pdf\"" % out_fn)
##                        self.request.RESPONSE.write(pdf_data)

                else:
                    raise Exception, "XXX pub_pref %s" % self.pub_pref