Total Complexity | 101 |
Total Lines | 758 |
Duplicated Lines | 5.41 % |
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 | # SENAITE.CORE is free software: you can redistribute it and/or modify it under |
||
6 | # the terms of the GNU General Public License as published by the Free Software |
||
7 | # Foundation, version 2. |
||
8 | # |
||
9 | # This program is distributed in the hope that it will be useful, but WITHOUT |
||
10 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
||
11 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
||
12 | # details. |
||
13 | # |
||
14 | # You should have received a copy of the GNU General Public License along with |
||
15 | # this program; if not, write to the Free Software Foundation, Inc., 51 |
||
16 | # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||
17 | # |
||
18 | # Copyright 2018-2019 by it's authors. |
||
19 | # Some rights reserved, see README and LICENSE. |
||
20 | |||
21 | import inspect |
||
22 | import itertools |
||
23 | from collections import OrderedDict |
||
24 | from string import Template |
||
25 | |||
26 | import transaction |
||
27 | from bika.lims import _ |
||
28 | from bika.lims import api |
||
29 | from bika.lims import logger |
||
30 | from bika.lims.api import mail as mailapi |
||
31 | from bika.lims.api.security import get_user |
||
32 | from bika.lims.api.security import get_user_id |
||
33 | from bika.lims.api.snapshot import take_snapshot |
||
34 | from bika.lims.decorators import returns_json |
||
35 | from bika.lims.utils import to_utf8 |
||
36 | from DateTime import DateTime |
||
37 | from plone.memoize import view |
||
38 | from Products.CMFCore.WorkflowCore import WorkflowException |
||
39 | from Products.CMFPlone.utils import safe_unicode |
||
40 | from Products.Five.browser import BrowserView |
||
41 | from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile |
||
42 | from ZODB.POSException import POSKeyError |
||
43 | from zope.interface import implements |
||
44 | from zope.publisher.interfaces import IPublishTraverse |
||
45 | |||
46 | DEFAULT_MAX_EMAIL_SIZE = 15 |
||
47 | |||
48 | |||
49 | class EmailView(BrowserView): |
||
50 | """Email Attachments View |
||
51 | """ |
||
52 | implements(IPublishTraverse) |
||
53 | |||
54 | template = ViewPageTemplateFile("templates/email.pt") |
||
55 | email_template = ViewPageTemplateFile("templates/email_template.pt") |
||
56 | |||
57 | def __init__(self, context, request): |
||
58 | super(EmailView, self).__init__(context, request) |
||
59 | # disable Plone's editable border |
||
60 | request.set("disable_border", True) |
||
61 | # list of requested subpaths |
||
62 | self.traverse_subpath = [] |
||
63 | # toggle to allow email sending |
||
64 | self.allow_send = True |
||
65 | |||
66 | def __call__(self): |
||
67 | # dispatch subpath request to `ajax_` methods |
||
68 | if len(self.traverse_subpath) > 0: |
||
69 | return self.handle_ajax_request() |
||
70 | |||
71 | # handle standard request |
||
72 | form = self.request.form |
||
73 | send = form.get("send", False) and True or False |
||
74 | cancel = form.get("cancel", False) and True or False |
||
75 | |||
76 | if send and self.validate_email_form(): |
||
77 | logger.info("*** PUBLISH SAMPLES & SEND REPORTS ***") |
||
78 | # 1. Publish all samples |
||
79 | self.publish_samples() |
||
80 | # 2. Notify all recipients |
||
81 | self.form_action_send() |
||
82 | |||
83 | elif cancel: |
||
84 | logger.info("*** CANCEL EMAIL PUBLICATION ***") |
||
85 | self.form_action_cancel() |
||
86 | |||
87 | else: |
||
88 | logger.info("*** RENDER EMAIL FORM ***") |
||
89 | # validate email size |
||
90 | self.validate_email_size() |
||
91 | # validate email recipients |
||
92 | self.validate_email_recipients() |
||
93 | |||
94 | return self.template() |
||
95 | |||
96 | def publishTraverse(self, request, name): |
||
97 | """Called before __call__ for each path name |
||
98 | |||
99 | Appends the path to the additional requested path after the view name |
||
100 | to the internal `traverse_subpath` list |
||
101 | """ |
||
102 | self.traverse_subpath.append(name) |
||
103 | return self |
||
104 | |||
105 | @returns_json |
||
106 | def handle_ajax_request(self): |
||
107 | """Handle requests ajax routes |
||
108 | """ |
||
109 | # check if the method exists |
||
110 | func_arg = self.traverse_subpath[0] |
||
111 | func_name = "ajax_{}".format(func_arg) |
||
112 | func = getattr(self, func_name, None) |
||
113 | |||
114 | if func is None: |
||
115 | return self.fail("Invalid function", status=400) |
||
116 | |||
117 | # Additional provided path segments after the function name are handled |
||
118 | # as positional arguments |
||
119 | args = self.traverse_subpath[1:] |
||
120 | |||
121 | # check mandatory arguments |
||
122 | func_sig = inspect.getargspec(func) |
||
123 | # positional arguments after `self` argument |
||
124 | required_args = func_sig.args[1:] |
||
125 | |||
126 | if len(args) < len(required_args): |
||
127 | return self.fail("Wrong signature, please use '{}/{}'" |
||
128 | .format(func_arg, "/".join(required_args)), 400) |
||
129 | return func(*args) |
||
130 | |||
131 | def form_action_send(self): |
||
132 | """Send form handler |
||
133 | """ |
||
134 | # send email to the selected recipients and responsibles |
||
135 | success = self.send_email(self.email_recipients_and_responsibles, |
||
136 | self.email_subject, |
||
137 | self.email_body, |
||
138 | attachments=self.email_attachments) |
||
139 | |||
140 | if success: |
||
141 | # write email sendlog log to keep track of the email submission |
||
142 | self.write_sendlog() |
||
143 | message = _(u"Message sent to {}".format( |
||
144 | ", ".join(self.email_recipients_and_responsibles))) |
||
145 | self.add_status_message(message, "info") |
||
146 | else: |
||
147 | message = _("Failed to send Email(s)") |
||
148 | self.add_status_message(message, "error") |
||
149 | |||
150 | self.request.response.redirect(self.exit_url) |
||
151 | |||
152 | def form_action_cancel(self): |
||
153 | """Cancel form handler |
||
154 | """ |
||
155 | self.add_status_message(_("Email cancelled"), "info") |
||
156 | self.request.response.redirect(self.exit_url) |
||
157 | |||
158 | def validate_email_form(self): |
||
159 | """Validate if the email form is complete for send |
||
160 | |||
161 | :returns: True if the validator passed, otherwise False |
||
162 | """ |
||
163 | if not self.email_recipients_and_responsibles: |
||
164 | message = _("No email recipients selected") |
||
165 | self.add_status_message(message, "error") |
||
166 | if not self.email_subject: |
||
167 | message = _("Please add an email subject") |
||
168 | self.add_status_message(message, "error") |
||
169 | if not self.email_body: |
||
170 | message = _("Please add an email text") |
||
171 | self.add_status_message(message, "error") |
||
172 | if not self.reports: |
||
173 | message = _("No reports found") |
||
174 | self.add_status_message(message, "error") |
||
175 | |||
176 | if not all([self.email_recipients_and_responsibles, |
||
177 | self.email_subject, |
||
178 | self.email_body, |
||
179 | self.reports]): |
||
180 | return False |
||
181 | return True |
||
182 | |||
183 | def validate_email_size(self): |
||
184 | """Validate if the email size exceeded the max. allowed size |
||
185 | |||
186 | :returns: True if the validator passed, otherwise False |
||
187 | """ |
||
188 | if self.total_size > self.max_email_size: |
||
189 | # don't allow to send oversized emails |
||
190 | self.allow_send = False |
||
191 | message = _("Total size of email exceeded {:.1f} MB ({:.2f} MB)" |
||
192 | .format(self.max_email_size / 1024, |
||
193 | self.total_size / 1024)) |
||
194 | self.add_status_message(message, "error") |
||
195 | return False |
||
196 | return True |
||
197 | |||
198 | def validate_email_recipients(self): |
||
199 | """Validate if the recipients are all valid |
||
200 | |||
201 | :returns: True if the validator passed, otherwise False |
||
202 | """ |
||
203 | # inform the user about invalid recipients |
||
204 | if not all(map(lambda r: r.get("valid"), self.recipients_data)): |
||
205 | message = _( |
||
206 | "Not all contacts are equal for the selected Reports. " |
||
207 | "Please manually select recipients for this email.") |
||
208 | self.add_status_message(message, "warning") |
||
209 | return False |
||
210 | return True |
||
211 | |||
212 | @property |
||
213 | def portal(self): |
||
214 | """Get the portal object |
||
215 | """ |
||
216 | return api.get_portal() |
||
217 | |||
218 | @property |
||
219 | def laboratory(self): |
||
220 | """Laboratory object from the LIMS setup |
||
221 | """ |
||
222 | return api.get_setup().laboratory |
||
223 | |||
224 | @property |
||
225 | @view.memoize |
||
226 | def reports(self): |
||
227 | """Return the objects from the UIDs given in the request |
||
228 | """ |
||
229 | # Create a mapping of source ARs for copy |
||
230 | uids = self.request.form.get("uids", []) |
||
231 | # handle 'uids' GET parameter coming from a redirect |
||
232 | if isinstance(uids, basestring): |
||
233 | uids = uids.split(",") |
||
234 | uids = filter(api.is_uid, uids) |
||
235 | unique_uids = OrderedDict().fromkeys(uids).keys() |
||
236 | return map(self.get_object_by_uid, unique_uids) |
||
237 | |||
238 | @property |
||
239 | @view.memoize |
||
240 | def attachments(self): |
||
241 | """Return the objects from the UIDs given in the request |
||
242 | """ |
||
243 | uids = self.request.form.get("attachment_uids", []) |
||
244 | return map(self.get_object_by_uid, uids) |
||
245 | |||
246 | @property |
||
247 | def email_sender_address(self): |
||
248 | """Sender email is either the lab email or portal email "from" address |
||
249 | """ |
||
250 | lab_email = self.laboratory.getEmailAddress() |
||
251 | portal_email = self.portal.email_from_address |
||
252 | return lab_email or portal_email |
||
253 | |||
254 | @property |
||
255 | def email_sender_name(self): |
||
256 | """Sender name is either the lab name or the portal email "from" name |
||
257 | """ |
||
258 | lab_from_name = self.laboratory.getName() |
||
259 | portal_from_name = self.portal.email_from_name |
||
260 | return lab_from_name or portal_from_name |
||
261 | |||
262 | @property |
||
263 | def email_recipients_and_responsibles(self): |
||
264 | """Returns a unified list of recipients and responsibles |
||
265 | """ |
||
266 | return list(set(self.email_recipients + self.email_responsibles)) |
||
267 | |||
268 | @property |
||
269 | def email_recipients(self): |
||
270 | """Email addresses of the selected recipients |
||
271 | """ |
||
272 | return map(safe_unicode, self.request.form.get("recipients", [])) |
||
273 | |||
274 | @property |
||
275 | def email_responsibles(self): |
||
276 | """Email addresses of the responsible persons |
||
277 | """ |
||
278 | return map(safe_unicode, self.request.form.get("responsibles", [])) |
||
279 | |||
280 | @property |
||
281 | def email_subject(self): |
||
282 | """Email subject line to be used in the template |
||
283 | """ |
||
284 | # request parameter has precedence |
||
285 | subject = self.request.get("subject", None) |
||
286 | if subject is not None: |
||
287 | return subject |
||
288 | subject = self.context.translate(_("Analysis Results for {}")) |
||
289 | return subject.format(self.client_name) |
||
290 | |||
291 | @property |
||
292 | def email_body(self): |
||
293 | """Email body text to be used in the template |
||
294 | """ |
||
295 | # request parameter has precedence |
||
296 | body = self.request.get("body", None) |
||
297 | if body is not None: |
||
298 | return body |
||
299 | return self.context.translate(_(self.email_template(self))) |
||
300 | |||
301 | @property |
||
302 | def email_attachments(self): |
||
303 | attachments = [] |
||
304 | |||
305 | # Convert report PDFs -> email attachments |
||
306 | for report in self.reports: |
||
307 | pdf = self.get_pdf(report) |
||
308 | if pdf is None: |
||
309 | logger.error("Skipping empty PDF for report {}" |
||
310 | .format(report.getId())) |
||
311 | continue |
||
312 | sample = report.getAnalysisRequest() |
||
313 | filename = "{}.pdf".format(api.get_id(sample)) |
||
314 | filedata = pdf.data |
||
315 | attachments.append( |
||
316 | mailapi.to_email_attachment(filedata, filename)) |
||
317 | |||
318 | # Convert additional attachments |
||
319 | for attachment in self.attachments: |
||
320 | af = attachment.getAttachmentFile() |
||
321 | filedata = af.data |
||
322 | filename = af.filename |
||
323 | attachments.append( |
||
324 | mailapi.to_email_attachment(filedata, filename)) |
||
325 | |||
326 | return attachments |
||
327 | |||
328 | @property |
||
329 | def reports_data(self): |
||
330 | """Returns a list of report data dictionaries |
||
331 | """ |
||
332 | reports = self.reports |
||
333 | return map(self.get_report_data, reports) |
||
334 | |||
335 | @property |
||
336 | def recipients_data(self): |
||
337 | """Returns a list of recipients data dictionaries |
||
338 | """ |
||
339 | reports = self.reports |
||
340 | return self.get_recipients_data(reports) |
||
341 | |||
342 | @property |
||
343 | def responsibles_data(self): |
||
344 | """Returns a list of responsibles data dictionaries |
||
345 | """ |
||
346 | reports = self.reports |
||
347 | return self.get_responsibles_data(reports) |
||
348 | |||
349 | @property |
||
350 | def client_name(self): |
||
351 | """Returns the client name |
||
352 | """ |
||
353 | return safe_unicode(self.context.Title()) |
||
354 | |||
355 | @property |
||
356 | def exit_url(self): |
||
357 | """Exit URL for redirect |
||
358 | """ |
||
359 | return "{}/{}".format( |
||
360 | api.get_url(self.context), "reports_listing") |
||
361 | |||
362 | @property |
||
363 | def total_size(self): |
||
364 | """Total size of all report PDFs + additional attachments |
||
365 | """ |
||
366 | reports = self.reports |
||
367 | attachments = self.attachments |
||
368 | return self.get_total_size(reports, attachments) |
||
369 | |||
370 | @property |
||
371 | def max_email_size(self): |
||
372 | """Return the max. allowed email size in KB |
||
373 | """ |
||
374 | # check first if a registry record exists |
||
375 | max_email_size = api.get_registry_record( |
||
376 | "senaite.core.max_email_size") |
||
377 | if max_email_size is None: |
||
378 | max_size = DEFAULT_MAX_EMAIL_SIZE |
||
379 | if max_size < 0: |
||
|
|||
380 | max_email_size = 0 |
||
381 | return max_size * 1024 |
||
382 | |||
383 | def make_sendlog_record(self, **kw): |
||
384 | """Create a new sendlog record |
||
385 | """ |
||
386 | user = get_user() |
||
387 | actor = get_user_id() |
||
388 | userprops = api.get_user_properties(user) |
||
389 | actor_fullname = userprops.get("fullname", actor) |
||
390 | email_send_date = DateTime() |
||
391 | email_recipients = self.email_recipients |
||
392 | email_responsibles = self.email_responsibles |
||
393 | email_subject = self.email_subject |
||
394 | email_body = self.render_email_template(self.email_body) |
||
395 | email_attachments = map(api.get_uid, self.attachments) |
||
396 | |||
397 | record = { |
||
398 | "actor": actor, |
||
399 | "actor_fullname": actor_fullname, |
||
400 | "email_send_date": email_send_date, |
||
401 | "email_recipients": email_recipients, |
||
402 | "email_responsibles": email_responsibles, |
||
403 | "email_subject": email_subject, |
||
404 | "email_body": email_body, |
||
405 | "email_attachments": email_attachments, |
||
406 | |||
407 | } |
||
408 | # keywords take precedence |
||
409 | record.update(kw) |
||
410 | return record |
||
411 | |||
412 | def write_sendlog(self): |
||
413 | """Write email sendlog |
||
414 | """ |
||
415 | timestamp = DateTime() |
||
416 | |||
417 | for report in self.reports: |
||
418 | # get the current sendlog records |
||
419 | records = report.getSendLog() |
||
420 | # create a new record with the current data |
||
421 | new_record = self.make_sendlog_record(email_send_date=timestamp) |
||
422 | # set the new record to the existing records |
||
423 | records.append(new_record) |
||
424 | report.setSendLog(records) |
||
425 | # reindex object to make changes visible in the snapshot |
||
426 | report.reindexObject() |
||
427 | # manually take a new snapshot |
||
428 | take_snapshot(report) |
||
429 | |||
430 | def publish_samples(self): |
||
431 | """Publish all samples of the reports |
||
432 | """ |
||
433 | reports = self.reports |
||
434 | for report in reports: |
||
435 | # publish the primary sample |
||
436 | primary_sample = report.getAnalysisRequest() |
||
437 | self.publish(primary_sample) |
||
438 | # publish the contained samples |
||
439 | contained_samples = report.getContainedAnalysisRequests() |
||
440 | for sample in contained_samples: |
||
441 | # skip the primary sample |
||
442 | if sample == primary_sample: |
||
443 | continue |
||
444 | self.publish(sample) |
||
445 | |||
446 | def publish(self, sample): |
||
447 | """Set status to prepublished/published/republished |
||
448 | """ |
||
449 | wf = api.get_tool("portal_workflow") |
||
450 | status = wf.getInfoFor(sample, "review_state") |
||
451 | transitions = {"verified": "publish", |
||
452 | "published": "republish"} |
||
453 | transition = transitions.get(status, "prepublish") |
||
454 | logger.info("Transitioning sample {}: {} -> {}".format( |
||
455 | api.get_id(sample), status, transition)) |
||
456 | try: |
||
457 | # Manually update the view on the database to avoid conflict errors |
||
458 | sample.getClient()._p_jar.sync() |
||
459 | # Perform WF transition |
||
460 | wf.doActionFor(sample, transition) |
||
461 | # Commit the changes |
||
462 | transaction.commit() |
||
463 | return True |
||
464 | except WorkflowException as e: |
||
465 | logger.error(e) |
||
466 | return False |
||
467 | |||
468 | def render_email_template(self, template): |
||
469 | """Return the rendered email template |
||
470 | |||
471 | This method interpolates the $recipients variable with the selected |
||
472 | recipients from the email form. |
||
473 | |||
474 | :params template: Email body text |
||
475 | :returns: Rendered email template |
||
476 | """ |
||
477 | |||
478 | recipients = self.email_recipients_and_responsibles |
||
479 | template_context = { |
||
480 | "recipients": "\n".join(recipients) |
||
481 | } |
||
482 | |||
483 | email_template = Template(safe_unicode(template)).safe_substitute( |
||
484 | **template_context) |
||
485 | |||
486 | return email_template |
||
487 | |||
488 | def send_email(self, recipients, subject, body, attachments=None): |
||
489 | """Prepare and send email to the recipients |
||
490 | |||
491 | :param recipients: a list of email or name,email strings |
||
492 | :param subject: the email subject |
||
493 | :param body: the email body |
||
494 | :param attachments: list of email attachments |
||
495 | :returns: True if all emails were sent, else False |
||
496 | """ |
||
497 | email_body = self.render_email_template(body) |
||
498 | |||
499 | success = [] |
||
500 | # Send one email per recipient |
||
501 | for recipient in recipients: |
||
502 | # N.B. we use just the email here to prevent this Postfix Error: |
||
503 | # Recipient address rejected: User unknown in local recipient table |
||
504 | pair = mailapi.parse_email_address(recipient) |
||
505 | to_address = pair[1] |
||
506 | mime_msg = mailapi.compose_email(self.email_sender_address, |
||
507 | to_address, |
||
508 | subject, |
||
509 | email_body, |
||
510 | attachments=attachments) |
||
511 | sent = mailapi.send_email(mime_msg) |
||
512 | if not sent: |
||
513 | logger.error("Could not send email to {}".format(pair)) |
||
514 | success.append(sent) |
||
515 | |||
516 | if not all(success): |
||
517 | return False |
||
518 | return True |
||
519 | |||
520 | def add_status_message(self, message, level="info"): |
||
521 | """Set a portal status message |
||
522 | """ |
||
523 | return self.context.plone_utils.addPortalMessage(message, level) |
||
524 | |||
525 | def get_report_data(self, report): |
||
526 | """Report data to be used in the template |
||
527 | """ |
||
528 | sample = report.getAnalysisRequest() |
||
529 | analyses = sample.getAnalyses(full_objects=True) |
||
530 | # merge together sample + analyses attachments |
||
531 | attachments = itertools.chain( |
||
532 | sample.getAttachment(), |
||
533 | *map(lambda an: an.getAttachment(), analyses)) |
||
534 | attachments_data = map(self.get_attachment_data, attachments) |
||
535 | pdf = self.get_pdf(report) |
||
536 | filesize = "{} Kb".format(self.get_filesize(pdf)) |
||
537 | filename = "{}.pdf".format(sample.getId()) |
||
538 | |||
539 | return { |
||
540 | "sample": sample, |
||
541 | "attachments": attachments_data, |
||
542 | "pdf": pdf, |
||
543 | "obj": report, |
||
544 | "uid": api.get_uid(report), |
||
545 | "filesize": filesize, |
||
546 | "filename": filename, |
||
547 | } |
||
548 | |||
549 | def get_attachment_data(self, attachment): |
||
550 | """Attachments data to be used in the template |
||
551 | """ |
||
552 | f = attachment.getAttachmentFile() |
||
553 | attachment_type = attachment.getAttachmentType() |
||
554 | attachment_keys = attachment.getAttachmentKeys() |
||
555 | filename = f.filename |
||
556 | filesize = self.get_filesize(f) |
||
557 | mimetype = f.getContentType() |
||
558 | report_option = attachment.getReportOption() |
||
559 | |||
560 | return { |
||
561 | "obj": attachment, |
||
562 | "attachment_type": attachment_type, |
||
563 | "attachment_keys": attachment_keys, |
||
564 | "file": f, |
||
565 | "uid": api.get_uid(attachment), |
||
566 | "filesize": filesize, |
||
567 | "filename": filename, |
||
568 | "mimetype": mimetype, |
||
569 | "report_option": report_option, |
||
570 | } |
||
571 | |||
572 | def get_recipients_data(self, reports): |
||
573 | """Recipients data to be used in the template |
||
574 | """ |
||
575 | if not reports: |
||
576 | return [] |
||
577 | |||
578 | recipients = [] |
||
579 | recipient_names = [] |
||
580 | |||
581 | for num, report in enumerate(reports): |
||
582 | sample = report.getAnalysisRequest() |
||
583 | # recipient names of this report |
||
584 | report_recipient_names = [] |
||
585 | for recipient in self.get_recipients(sample): |
||
586 | name = recipient.get("Fullname") |
||
587 | email = recipient.get("EmailAddress") |
||
588 | address = mailapi.to_email_address(email, name=name) |
||
589 | record = { |
||
590 | "name": name, |
||
591 | "email": email, |
||
592 | "address": address, |
||
593 | "valid": True, |
||
594 | } |
||
595 | if record not in recipients: |
||
596 | recipients.append(record) |
||
597 | # remember the name of the recipient for this report |
||
598 | report_recipient_names.append(name) |
||
599 | recipient_names.append(report_recipient_names) |
||
600 | |||
601 | # recipient names, which all of the reports have in common |
||
602 | common_names = set(recipient_names[0]).intersection(*recipient_names) |
||
603 | # mark recipients not in common |
||
604 | for recipient in recipients: |
||
605 | if recipient.get("name") not in common_names: |
||
606 | recipient["valid"] = False |
||
607 | return recipients |
||
608 | |||
609 | def get_responsibles_data(self, reports): |
||
610 | """Responsibles data to be used in the template |
||
611 | """ |
||
612 | if not reports: |
||
613 | return [] |
||
614 | |||
615 | recipients = [] |
||
616 | recipient_names = [] |
||
617 | |||
618 | for num, report in enumerate(reports): |
||
619 | # get the linked AR of this ARReport |
||
620 | ar = report.getAnalysisRequest() |
||
621 | |||
622 | # recipient names of this report |
||
623 | report_recipient_names = [] |
||
624 | responsibles = ar.getResponsible() |
||
625 | for manager_id in responsibles.get("ids", []): |
||
626 | responsible = responsibles["dict"][manager_id] |
||
627 | name = responsible.get("name") |
||
628 | email = responsible.get("email") |
||
629 | address = mailapi.to_email_address(email, name=name) |
||
630 | record = { |
||
631 | "name": name, |
||
632 | "email": email, |
||
633 | "address": address, |
||
634 | "valid": True, |
||
635 | } |
||
636 | if record not in recipients: |
||
637 | recipients.append(record) |
||
638 | # remember the name of the recipient for this report |
||
639 | report_recipient_names.append(name) |
||
640 | recipient_names.append(report_recipient_names) |
||
641 | |||
642 | # recipient names, which all of the reports have in common |
||
643 | common_names = set(recipient_names[0]).intersection(*recipient_names) |
||
644 | # mark recipients not in common |
||
645 | for recipient in recipients: |
||
646 | if recipient.get("name") not in common_names: |
||
647 | recipient["valid"] = False |
||
648 | |||
649 | return recipients |
||
650 | |||
651 | def get_total_size(self, *files): |
||
652 | """Calculate the total size of the given files |
||
653 | """ |
||
654 | |||
655 | # Recursive unpack an eventual list of lists |
||
656 | def iterate(item): |
||
657 | if isinstance(item, (list, tuple)): |
||
658 | for i in item: |
||
659 | for ii in iterate(i): |
||
660 | yield ii |
||
661 | else: |
||
662 | yield item |
||
663 | |||
664 | # Calculate the total size of the given objects starting with an |
||
665 | # initial size of 0 |
||
666 | return reduce(lambda x, y: x + y, |
||
667 | map(self.get_filesize, iterate(files)), 0) |
||
668 | |||
669 | def get_object_by_uid(self, uid): |
||
670 | """Get the object by UID |
||
671 | """ |
||
672 | logger.debug("get_object_by_uid::UID={}".format(uid)) |
||
673 | obj = api.get_object_by_uid(uid, None) |
||
674 | if obj is None: |
||
675 | logger.warn("!! No object found for UID #{} !!") |
||
676 | return obj |
||
677 | |||
678 | def get_filesize(self, f): |
||
679 | """Return the filesize of the PDF as a float |
||
680 | """ |
||
681 | try: |
||
682 | filesize = float(f.get_size()) |
||
683 | return float("%.2f" % (filesize / 1024)) |
||
684 | except (POSKeyError, TypeError, AttributeError): |
||
685 | return 0.0 |
||
686 | |||
687 | def get_pdf(self, obj): |
||
688 | """Get the report PDF |
||
689 | """ |
||
690 | try: |
||
691 | return obj.getPdf() |
||
692 | except (POSKeyError, TypeError): |
||
693 | return None |
||
694 | |||
695 | View Code Duplication | def get_recipients(self, ar): |
|
696 | """Return the AR recipients in the same format like the AR Report |
||
697 | expects in the records field `Recipients` |
||
698 | """ |
||
699 | plone_utils = api.get_tool("plone_utils") |
||
700 | |||
701 | def is_email(email): |
||
702 | if not plone_utils.validateSingleEmailAddress(email): |
||
703 | return False |
||
704 | return True |
||
705 | |||
706 | def recipient_from_contact(contact): |
||
707 | if not contact: |
||
708 | return None |
||
709 | email = contact.getEmailAddress() |
||
710 | return { |
||
711 | "UID": api.get_uid(contact), |
||
712 | "Username": contact.getUsername(), |
||
713 | "Fullname": to_utf8(contact.Title()), |
||
714 | "EmailAddress": email, |
||
715 | } |
||
716 | |||
717 | def recipient_from_email(email): |
||
718 | if not is_email(email): |
||
719 | return None |
||
720 | return { |
||
721 | "UID": "", |
||
722 | "Username": "", |
||
723 | "Fullname": email, |
||
724 | "EmailAddress": email, |
||
725 | } |
||
726 | |||
727 | # Primary Contacts |
||
728 | to = filter(None, [recipient_from_contact(ar.getContact())]) |
||
729 | # CC Contacts |
||
730 | cc = filter(None, map(recipient_from_contact, ar.getCCContact())) |
||
731 | # CC Emails |
||
732 | cc_emails = map(lambda x: x.strip(), ar.getCCEmails().split(",")) |
||
733 | cc_emails = filter(None, map(recipient_from_email, cc_emails)) |
||
734 | |||
735 | return to + cc + cc_emails |
||
736 | |||
737 | def ajax_recalculate_size(self): |
||
738 | """Recalculate the total size of the selected attachments |
||
739 | """ |
||
740 | reports = self.reports |
||
741 | attachments = self.attachments |
||
742 | total_size = self.get_total_size(reports, attachments) |
||
743 | |||
744 | return { |
||
745 | "files": len(reports) + len(attachments), |
||
746 | "size": "%.2f" % total_size, |
||
747 | "limit": self.max_email_size, |
||
748 | "limit_exceeded": total_size > self.max_email_size, |
||
749 | } |
||
750 | |||
751 | def fail(self, message, status=500, **kw): |
||
752 | """Set a JSON error object and a status to the response |
||
753 | """ |
||
754 | self.request.response.setStatus(status) |
||
755 | result = {"success": False, "errors": message, "status": status} |
||
756 | result.update(kw) |
||
757 | return result |
||
758 |