Total Complexity | 91 |
Total Lines | 645 |
Duplicated Lines | 6.36 % |
Changes | 0 |
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like bika.lims.browser.publish.emailview often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
1 | # -*- coding: utf-8 -*- |
||
2 | # |
||
3 | # This file is part of SENAITE.CORE |
||
4 | # |
||
5 | # Copyright 2018 by it's authors. |
||
6 | |||
7 | import inspect |
||
8 | import mimetypes |
||
9 | import socket |
||
10 | from collections import OrderedDict |
||
11 | from email import encoders |
||
12 | from email.header import Header |
||
13 | from email.mime.base import MIMEBase |
||
14 | from email.mime.multipart import MIMEMultipart |
||
15 | from email.mime.text import MIMEText |
||
16 | from email.Utils import formataddr |
||
17 | from smtplib import SMTPException |
||
18 | from string import Template |
||
19 | |||
20 | from bika.lims import logger |
||
21 | from bika.lims.utils import to_utf8 |
||
22 | from Products.CMFCore.WorkflowCore import WorkflowException |
||
23 | from Products.CMFPlone.utils import safe_unicode |
||
24 | from Products.Five.browser import BrowserView |
||
25 | from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile |
||
26 | from bika.lims import api |
||
27 | from bika.lims import _ |
||
28 | from bika.lims.decorators import returns_json |
||
29 | from ZODB.POSException import POSKeyError |
||
30 | from zope.interface import implements |
||
31 | from zope.publisher.interfaces import IPublishTraverse |
||
32 | |||
33 | EMAIL_MAX_SIZE = 15 |
||
34 | |||
35 | |||
36 | class EmailView(BrowserView): |
||
37 | """Email Attachments View |
||
38 | """ |
||
39 | implements(IPublishTraverse) |
||
40 | |||
41 | template = ViewPageTemplateFile("templates/email.pt") |
||
42 | email_template = ViewPageTemplateFile("templates/email_template.pt") |
||
43 | |||
44 | def __init__(self, context, request): |
||
45 | super(EmailView, self).__init__(context, request) |
||
46 | # disable Plone's editable border |
||
47 | request.set("disable_border", True) |
||
48 | # remember context/request |
||
49 | self.context = context |
||
50 | self.request = request |
||
51 | self.url = self.context.absolute_url() |
||
52 | # the URL to redirect on cancel or after send |
||
53 | self.exit_url = "{}/{}".format(self.url, "reports_listing") |
||
54 | # we need to transform the title to unicode, so that we can use it for |
||
55 | self.client_name = safe_unicode(self.context.Title()) |
||
56 | self.email_body = self.context.translate(_(self.email_template(self))) |
||
57 | # string interpolation later |
||
58 | # N.B. We need to translate the raw string before interpolation |
||
59 | subject = self.context.translate(_("Analysis Results for {}")) |
||
60 | self.email_subject = subject.format(self.client_name) |
||
61 | self.allow_send = True |
||
62 | self.traverse_subpath = [] |
||
63 | |||
64 | def __call__(self): |
||
65 | # handle subpath request |
||
66 | if len(self.traverse_subpath) > 0: |
||
67 | return self.handle_ajax_request() |
||
68 | # handle standard request |
||
69 | return self.handle_http_request() |
||
70 | |||
71 | def publishTraverse(self, request, name): |
||
72 | """Called before __call__ for each path name |
||
73 | """ |
||
74 | self.traverse_subpath.append(name) |
||
75 | return self |
||
76 | |||
77 | def fail(self, message, status=500, **kw): |
||
78 | """Set a JSON error object and a status to the response |
||
79 | """ |
||
80 | self.request.response.setStatus(status) |
||
81 | result = {"success": False, "errors": message, "status": status} |
||
82 | result.update(kw) |
||
83 | return result |
||
84 | |||
85 | @returns_json |
||
86 | def handle_ajax_request(self): |
||
87 | """Handle requests ajax routes |
||
88 | """ |
||
89 | # check if the method exists |
||
90 | func_arg = self.traverse_subpath[0] |
||
91 | func_name = "ajax_{}".format(func_arg) |
||
92 | func = getattr(self, func_name, None) |
||
93 | |||
94 | if func is None: |
||
95 | return self.fail("Invalid function", status=400) |
||
96 | |||
97 | # Additional provided path segments after the function name are handled |
||
98 | # as positional arguments |
||
99 | args = self.traverse_subpath[1:] |
||
100 | |||
101 | # check mandatory arguments |
||
102 | func_sig = inspect.getargspec(func) |
||
103 | # positional arguments after `self` argument |
||
104 | required_args = func_sig.args[1:] |
||
105 | |||
106 | if len(args) < len(required_args): |
||
107 | return self.fail("Wrong signature, please use '{}/{}'" |
||
108 | .format(func_arg, "/".join(required_args)), 400) |
||
109 | return func(*args) |
||
110 | |||
111 | def handle_http_request(self): |
||
112 | request = self.request |
||
113 | form = request.form |
||
114 | |||
115 | submitted = form.get("submitted", False) |
||
116 | send = form.get("send", False) |
||
117 | cancel = form.get("cancel", False) |
||
118 | |||
119 | if submitted and send: |
||
120 | logger.info("*** SENDING EMAIL ***") |
||
121 | |||
122 | # Parse used defined values from the request form |
||
123 | recipients = form.get("recipients", []) |
||
124 | responsibles = form.get("responsibles", []) |
||
125 | subject = form.get("subject") |
||
126 | body = form.get("body") |
||
127 | reports = self.get_reports() |
||
128 | |||
129 | # Merge recipiens and responsibles |
||
130 | recipients = set(recipients + responsibles) |
||
131 | |||
132 | # sanity checks |
||
133 | if not recipients: |
||
134 | message = _("No email recipients selected") |
||
135 | self.add_status_message(message, "error") |
||
136 | if not subject: |
||
137 | message = _("Please add an email subject") |
||
138 | self.add_status_message(message, "error") |
||
139 | if not body: |
||
140 | message = _("Please add an email text") |
||
141 | self.add_status_message(message, "error") |
||
142 | if not reports: |
||
143 | message = _("No attachments") |
||
144 | self.add_status_message(message, "error") |
||
145 | |||
146 | success = False |
||
147 | if all([recipients, subject, body, reports]): |
||
148 | attachments = [] |
||
149 | |||
150 | # report pdfs |
||
151 | for report in reports: |
||
152 | pdf = self.get_pdf(report) |
||
153 | if pdf is None: |
||
154 | logger.error("Skipping empty PDF for report {}" |
||
155 | .format(report.getId())) |
||
156 | continue |
||
157 | ar = report.getAnalysisRequest() |
||
158 | filename = "{}.pdf".format(ar.getId()) |
||
159 | filedata = pdf.data |
||
160 | attachments.append( |
||
161 | self.to_email_attachment(filename, filedata)) |
||
162 | |||
163 | # additional attachments |
||
164 | for attachment in self.get_attachments(): |
||
165 | af = attachment.getAttachmentFile() |
||
166 | filedata = af.data |
||
167 | filename = af.filename |
||
168 | attachments.append( |
||
169 | self.to_email_attachment(filename, filedata)) |
||
170 | |||
171 | success = self.send_email( |
||
172 | recipients, subject, body, attachments=attachments) |
||
173 | |||
174 | if success: |
||
175 | # selected name, email pairs which received the email |
||
176 | pairs = map(self.parse_email, recipients) |
||
177 | send_to_names = map(lambda p: p[0], pairs) |
||
178 | |||
179 | # set recipients to the reports |
||
180 | for report in reports: |
||
181 | ar = report.getAnalysisRequest() |
||
182 | # publish the AR |
||
183 | self.publish(ar) |
||
184 | |||
185 | # Publish all linked ARs of this report |
||
186 | # N.B. `ContainedAnalysisRequests` is an extended field |
||
187 | field = report.getField("ContainedAnalysisRequests") |
||
188 | contained_ars = field.get(report) or [] |
||
189 | for obj in contained_ars: |
||
190 | self.publish(obj) |
||
191 | |||
192 | # add new recipients to the AR Report |
||
193 | new_recipients = filter( |
||
194 | lambda r: r.get("Fullname") in send_to_names, |
||
|
|||
195 | self.get_recipients(ar)) |
||
196 | self.set_report_recipients(report, new_recipients) |
||
197 | |||
198 | message = _(u"Message sent to {}" |
||
199 | .format(", ".join(send_to_names))) |
||
200 | self.add_status_message(message, "info") |
||
201 | return request.response.redirect(self.exit_url) |
||
202 | else: |
||
203 | message = _("Failed to send Email(s)") |
||
204 | self.add_status_message(message, "error") |
||
205 | |||
206 | if submitted and cancel: |
||
207 | logger.info("*** EMAIL CANCELLED ***") |
||
208 | message = _("Email cancelled") |
||
209 | self.add_status_message(message, "info") |
||
210 | return request.response.redirect(self.exit_url) |
||
211 | |||
212 | # get the selected ARReport objects |
||
213 | reports = self.get_reports() |
||
214 | attachments = self.get_attachments() |
||
215 | |||
216 | # calculate the total size of all PDFs |
||
217 | self.total_size = self.get_total_size(reports, attachments) |
||
218 | if self.total_size > self.max_email_size: |
||
219 | # don't allow to send oversized emails |
||
220 | self.allow_send = False |
||
221 | message = _("Total size of email exceeded {:.1f} MB ({:.2f} MB)" |
||
222 | .format(self.max_email_size / 1024, |
||
223 | self.total_size / 1024)) |
||
224 | self.add_status_message(message, "error") |
||
225 | |||
226 | # prepare the data for the template |
||
227 | self.reports = map(self.get_report_data, reports) |
||
228 | self.recipients = self.get_recipients_data(reports) |
||
229 | self.responsibles = self.get_responsibles_data(reports) |
||
230 | |||
231 | # inform the user about invalid recipients |
||
232 | if not all(map(lambda r: r.get("valid"), self.recipients)): |
||
233 | message = _( |
||
234 | "Not all contacts are equal for the selected Reports. " |
||
235 | "Please manually select recipients for this email.") |
||
236 | self.add_status_message(message, "warning") |
||
237 | |||
238 | return self.template() |
||
239 | |||
240 | def set_report_recipients(self, report, recipients): |
||
241 | """Set recipients to the reports w/o overwriting the old ones |
||
242 | |||
243 | :param reports: list of ARReports |
||
244 | :param recipients: list of name,email strings |
||
245 | """ |
||
246 | to_set = report.getRecipients() |
||
247 | for recipient in recipients: |
||
248 | if recipient not in to_set: |
||
249 | to_set.append(recipient) |
||
250 | report.setRecipients(to_set) |
||
251 | |||
252 | def publish(self, ar): |
||
253 | """Set status to prepublished/published/republished |
||
254 | """ |
||
255 | wf = api.get_tool("portal_workflow") |
||
256 | status = wf.getInfoFor(ar, "review_state") |
||
257 | transitions = {"verified": "publish", |
||
258 | "published": "republish"} |
||
259 | transition = transitions.get(status, "prepublish") |
||
260 | logger.info("AR Transition: {} -> {}".format(status, transition)) |
||
261 | try: |
||
262 | wf.doActionFor(ar, transition) |
||
263 | return True |
||
264 | except WorkflowException as e: |
||
265 | logger.debug(e) |
||
266 | return False |
||
267 | |||
268 | def parse_email(self, email): |
||
269 | """parse an email to an unicode name, email tuple |
||
270 | """ |
||
271 | splitted = safe_unicode(email).rsplit(",", 1) |
||
272 | if len(splitted) == 1: |
||
273 | return (False, splitted[0]) |
||
274 | elif len(splitted) == 2: |
||
275 | return (splitted[0], splitted[1]) |
||
276 | else: |
||
277 | raise ValueError("Could not parse email '{}'".format(email)) |
||
278 | |||
279 | def to_email_attachment(self, filename, filedata, **kw): |
||
280 | """Create a new MIME Attachment |
||
281 | |||
282 | The Content-Type: header is build from the maintype and subtype of the |
||
283 | guessed filename mimetype. Additional parameters for this header are |
||
284 | taken from the keyword arguments. |
||
285 | """ |
||
286 | maintype = "application" |
||
287 | subtype = "octet-stream" |
||
288 | |||
289 | mime_type = mimetypes.guess_type(filename)[0] |
||
290 | if mime_type is not None: |
||
291 | maintype, subtype = mime_type.split("/") |
||
292 | |||
293 | attachment = MIMEBase(maintype, subtype, **kw) |
||
294 | attachment.set_payload(filedata) |
||
295 | encoders.encode_base64(attachment) |
||
296 | attachment.add_header("Content-Disposition", |
||
297 | "attachment; filename=%s" % filename) |
||
298 | return attachment |
||
299 | |||
300 | def send_email(self, recipients, subject, body, attachments=None): |
||
301 | """Prepare and send email to the recipients |
||
302 | |||
303 | :param recipients: a list of email or name,email strings |
||
304 | :param subject: the email subject |
||
305 | :param body: the email body |
||
306 | :param attachments: list of email attachments |
||
307 | :returns: True if all emails were sent, else false |
||
308 | """ |
||
309 | |||
310 | recipient_pairs = map(self.parse_email, recipients) |
||
311 | template_context = { |
||
312 | "recipients": "\n".join( |
||
313 | map(lambda p: formataddr(p), recipient_pairs)) |
||
314 | } |
||
315 | |||
316 | body_template = Template(safe_unicode(body)).safe_substitute( |
||
317 | **template_context) |
||
318 | |||
319 | _preamble = "This is a multi-part message in MIME format.\n" |
||
320 | _from = formataddr((self.email_from_name, self.email_from_address)) |
||
321 | _subject = Header(s=safe_unicode(subject), charset="utf8") |
||
322 | _body = MIMEText(body_template, _subtype="plain", _charset="utf8") |
||
323 | |||
324 | # Create the enclosing message |
||
325 | mime_msg = MIMEMultipart() |
||
326 | mime_msg.preamble = _preamble |
||
327 | mime_msg["Subject"] = _subject |
||
328 | mime_msg["From"] = _from |
||
329 | mime_msg.attach(_body) |
||
330 | |||
331 | # Attach attachments |
||
332 | for attachment in attachments: |
||
333 | mime_msg.attach(attachment) |
||
334 | |||
335 | success = [] |
||
336 | # Send one email per recipient |
||
337 | for pair in recipient_pairs: |
||
338 | # N.B.: Headers are added additive, so we need to remove any |
||
339 | # existing "To" headers |
||
340 | # No KeyError is raised if the key does not exist. |
||
341 | # https://docs.python.org/2/library/email.message.html#email.message.Message.__delitem__ |
||
342 | del mime_msg["To"] |
||
343 | |||
344 | # N.B. we use just the email here to prevent this Postfix Error: |
||
345 | # Recipient address rejected: User unknown in local recipient table |
||
346 | mime_msg["To"] = pair[1] |
||
347 | msg_string = mime_msg.as_string() |
||
348 | sent = self.send(msg_string) |
||
349 | if not sent: |
||
350 | logger.error("Could not send email to {}".format(pair)) |
||
351 | success.append(sent) |
||
352 | |||
353 | if not all(success): |
||
354 | return False |
||
355 | return True |
||
356 | |||
357 | def send(self, msg_string, immediate=True): |
||
358 | """Send the email via the MailHost tool |
||
359 | """ |
||
360 | try: |
||
361 | mailhost = api.get_tool("MailHost") |
||
362 | mailhost.send(msg_string, immediate=immediate) |
||
363 | except SMTPException as e: |
||
364 | logger.error(e) |
||
365 | return False |
||
366 | except socket.error as e: |
||
367 | logger.error(e) |
||
368 | return False |
||
369 | return True |
||
370 | |||
371 | def add_status_message(self, message, level="info"): |
||
372 | """Set a portal status message |
||
373 | """ |
||
374 | return self.context.plone_utils.addPortalMessage(message, level) |
||
375 | |||
376 | def get_report_data(self, report): |
||
377 | """Report data to be used in the template |
||
378 | """ |
||
379 | ar = report.getAnalysisRequest() |
||
380 | attachments = map(self.get_attachment_data, ar.getAttachment()) |
||
381 | pdf = self.get_pdf(report) |
||
382 | filesize = "{} Kb".format(self.get_filesize(pdf)) |
||
383 | filename = "{}.pdf".format(ar.getId()) |
||
384 | |||
385 | return { |
||
386 | "ar": ar, |
||
387 | "attachments": attachments, |
||
388 | "pdf": pdf, |
||
389 | "obj": report, |
||
390 | "uid": api.get_uid(report), |
||
391 | "filesize": filesize, |
||
392 | "filename": filename, |
||
393 | } |
||
394 | |||
395 | def get_attachment_data(self, attachment): |
||
396 | """Attachments data |
||
397 | """ |
||
398 | f = attachment.getAttachmentFile() |
||
399 | attachment_type = attachment.getAttachmentType() |
||
400 | attachment_keys = attachment.getAttachmentKeys() |
||
401 | filename = f.filename |
||
402 | filesize = self.get_filesize(f) |
||
403 | mimetype = f.getContentType() |
||
404 | report_option = attachment.getReportOption() |
||
405 | |||
406 | return { |
||
407 | "obj": attachment, |
||
408 | "attachment_type": attachment_type, |
||
409 | "attachment_keys": attachment_keys, |
||
410 | "file": f, |
||
411 | "uid": api.get_uid(attachment), |
||
412 | "filesize": filesize, |
||
413 | "filename": filename, |
||
414 | "mimetype": mimetype, |
||
415 | "report_option": report_option, |
||
416 | } |
||
417 | |||
418 | def get_recipients_data(self, reports): |
||
419 | """Recipients data to be used in the template |
||
420 | """ |
||
421 | if not reports: |
||
422 | return [] |
||
423 | |||
424 | recipients = [] |
||
425 | recipient_names = [] |
||
426 | |||
427 | for num, report in enumerate(reports): |
||
428 | # get the linked AR of this ARReport |
||
429 | ar = report.getAnalysisRequest() |
||
430 | # recipient names of this report |
||
431 | report_recipient_names = [] |
||
432 | for recipient in self.get_recipients(ar): |
||
433 | name = recipient.get("Fullname") |
||
434 | email = recipient.get("EmailAddress") |
||
435 | record = { |
||
436 | "name": name, |
||
437 | "email": email, |
||
438 | "valid": True, |
||
439 | } |
||
440 | if record not in recipients: |
||
441 | recipients.append(record) |
||
442 | # remember the name of the recipient for this report |
||
443 | report_recipient_names.append(name) |
||
444 | recipient_names.append(report_recipient_names) |
||
445 | |||
446 | # recipient names, which all of the reports have in common |
||
447 | common_names = set(recipient_names[0]).intersection(*recipient_names) |
||
448 | # mark recipients not in common |
||
449 | for recipient in recipients: |
||
450 | if recipient.get("name") not in common_names: |
||
451 | recipient["valid"] = False |
||
452 | return recipients |
||
453 | |||
454 | def get_responsibles_data(self, reports): |
||
455 | """Responsibles data to be used in the template |
||
456 | """ |
||
457 | if not reports: |
||
458 | return [] |
||
459 | |||
460 | recipients = [] |
||
461 | recipient_names = [] |
||
462 | |||
463 | for num, report in enumerate(reports): |
||
464 | # get the linked AR of this ARReport |
||
465 | ar = report.getAnalysisRequest() |
||
466 | |||
467 | # recipient names of this report |
||
468 | report_recipient_names = [] |
||
469 | responsibles = ar.getResponsible() |
||
470 | for manager_id in responsibles.get("ids", []): |
||
471 | responsible = responsibles["dict"][manager_id] |
||
472 | name = responsible.get("name") |
||
473 | email = responsible.get("email") |
||
474 | record = { |
||
475 | "name": name, |
||
476 | "email": email, |
||
477 | "valid": True, |
||
478 | } |
||
479 | if record not in recipients: |
||
480 | recipients.append(record) |
||
481 | # remember the name of the recipient for this report |
||
482 | report_recipient_names.append(name) |
||
483 | recipient_names.append(report_recipient_names) |
||
484 | |||
485 | # recipient names, which all of the reports have in common |
||
486 | common_names = set(recipient_names[0]).intersection(*recipient_names) |
||
487 | # mark recipients not in common |
||
488 | for recipient in recipients: |
||
489 | if recipient.get("name") not in common_names: |
||
490 | recipient["valid"] = False |
||
491 | |||
492 | return recipients |
||
493 | |||
494 | @property |
||
495 | def portal(self): |
||
496 | return api.get_portal() |
||
497 | |||
498 | @property |
||
499 | def laboratory(self): |
||
500 | return api.get_setup().laboratory |
||
501 | |||
502 | @property |
||
503 | def email_from_address(self): |
||
504 | """Portal email |
||
505 | """ |
||
506 | lab_email = self.laboratory.getEmailAddress() |
||
507 | portal_email = self.portal.email_from_address |
||
508 | return lab_email or portal_email |
||
509 | |||
510 | @property |
||
511 | def email_from_name(self): |
||
512 | """Portal email name |
||
513 | """ |
||
514 | lab_from_name = self.laboratory.getName() |
||
515 | portal_from_name = self.portal.email_from_name |
||
516 | return lab_from_name or portal_from_name |
||
517 | |||
518 | def get_total_size(self, *files): |
||
519 | """Calculate the total size of the given files |
||
520 | """ |
||
521 | |||
522 | # Recursive unpack an eventual list of lists |
||
523 | def iterate(item): |
||
524 | if isinstance(item, (list, tuple)): |
||
525 | for i in item: |
||
526 | for ii in iterate(i): |
||
527 | yield ii |
||
528 | else: |
||
529 | yield item |
||
530 | |||
531 | # Calculate the total size of the given objects starting with an |
||
532 | # initial size of 0 |
||
533 | return reduce(lambda x, y: x + y, |
||
534 | map(self.get_filesize, iterate(files)), 0) |
||
535 | |||
536 | @property |
||
537 | def max_email_size(self): |
||
538 | """Return the max. allowed email size in KB |
||
539 | """ |
||
540 | # TODO: Refactor to customizable setup option |
||
541 | max_size = EMAIL_MAX_SIZE |
||
542 | if max_size < 0: |
||
543 | return 0.0 |
||
544 | return max_size * 1024 |
||
545 | |||
546 | def get_reports(self): |
||
547 | """Return the objects from the UIDs given in the request |
||
548 | """ |
||
549 | # Create a mapping of source ARs for copy |
||
550 | uids = self.request.form.get("uids", []) |
||
551 | # handle 'uids' GET parameter coming from a redirect |
||
552 | if isinstance(uids, basestring): |
||
553 | uids = uids.split(",") |
||
554 | uids = filter(api.is_uid, uids) |
||
555 | unique_uids = OrderedDict().fromkeys(uids).keys() |
||
556 | return map(self.get_object_by_uid, unique_uids) |
||
557 | |||
558 | def get_attachments(self): |
||
559 | """Return the objects from the UIDs given in the request |
||
560 | """ |
||
561 | # Create a mapping of source ARs for copy |
||
562 | uids = self.request.form.get("attachment_uids", []) |
||
563 | return map(self.get_object_by_uid, uids) |
||
564 | |||
565 | def get_object_by_uid(self, uid): |
||
566 | """Get the object by UID |
||
567 | """ |
||
568 | logger.debug("get_object_by_uid::UID={}".format(uid)) |
||
569 | obj = api.get_object_by_uid(uid, None) |
||
570 | if obj is None: |
||
571 | logger.warn("!! No object found for UID #{} !!") |
||
572 | return obj |
||
573 | |||
574 | def get_filesize(self, f): |
||
575 | """Return the filesize of the PDF as a float |
||
576 | """ |
||
577 | try: |
||
578 | filesize = float(f.get_size()) |
||
579 | return float("%.2f" % (filesize / 1024)) |
||
580 | except (POSKeyError, TypeError, AttributeError): |
||
581 | return 0.0 |
||
582 | |||
583 | def get_pdf(self, obj): |
||
584 | """Get the report PDF |
||
585 | """ |
||
586 | try: |
||
587 | return obj.getPdf() |
||
588 | except (POSKeyError, TypeError): |
||
589 | return None |
||
590 | |||
591 | View Code Duplication | def get_recipients(self, ar): |
|
592 | """Return the AR recipients in the same format like the AR Report |
||
593 | expects in the records field `Recipients` |
||
594 | """ |
||
595 | plone_utils = api.get_tool("plone_utils") |
||
596 | |||
597 | def is_email(email): |
||
598 | if not plone_utils.validateSingleEmailAddress(email): |
||
599 | return False |
||
600 | return True |
||
601 | |||
602 | def recipient_from_contact(contact): |
||
603 | if not contact: |
||
604 | return None |
||
605 | email = contact.getEmailAddress() |
||
606 | return { |
||
607 | "UID": api.get_uid(contact), |
||
608 | "Username": contact.getUsername(), |
||
609 | "Fullname": to_utf8(contact.Title()), |
||
610 | "EmailAddress": email, |
||
611 | } |
||
612 | |||
613 | def recipient_from_email(email): |
||
614 | if not is_email(email): |
||
615 | return None |
||
616 | return { |
||
617 | "UID": "", |
||
618 | "Username": "", |
||
619 | "Fullname": email, |
||
620 | "EmailAddress": email, |
||
621 | } |
||
622 | |||
623 | # Primary Contacts |
||
624 | to = filter(None, [recipient_from_contact(ar.getContact())]) |
||
625 | # CC Contacts |
||
626 | cc = filter(None, map(recipient_from_contact, ar.getCCContact())) |
||
627 | # CC Emails |
||
628 | cc_emails = map(lambda x: x.strip(), ar.getCCEmails().split(",")) |
||
629 | cc_emails = filter(None, map(recipient_from_email, cc_emails)) |
||
630 | |||
631 | return to + cc + cc_emails |
||
632 | |||
633 | def ajax_recalculate_size(self): |
||
634 | """Recalculate the total size of the selected attachments |
||
635 | """ |
||
636 | reports = self.get_reports() |
||
637 | attachments = self.get_attachments() |
||
638 | total_size = self.get_total_size(reports, attachments) |
||
639 | |||
640 | return { |
||
641 | "files": len(reports) + len(attachments), |
||
642 | "size": "%.2f" % total_size, |
||
643 | "limit": self.max_email_size, |
||
644 | "limit_exceeded": total_size > self.max_email_size, |
||
645 | } |
||
646 |