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)
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
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')
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)
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
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']
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()
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
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
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)
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
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
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))
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
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)
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
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
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))
def test_max_retry_after_smtp_disconnect(self): self._test_max_retry_limit_causes_failure( SMTPServerDisconnected(425, "Disconnecting"))
def test_retry_after_smtp_disconnect(self): self._test_retry_after_limited_retry_error( SMTPServerDisconnected(425, "Disconnecting"))
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]
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