def create_report(self, parent, pdf, html, uids, metadata): """Create a new report object NOTE: We limit the creation of reports to 1 to avoid conflict errors on simultaneous publication. :param parent: parent object where to create the report inside :returns: ARReport """ parent_id = api.get_id(parent) logger.info("Create Report for {} ...".format(parent_id)) # Manually update the view on the database to avoid conflict errors parent._p_jar.sync() # Create the report object report = api.create(parent, "ARReport", AnalysisRequest=api.get_uid(parent), Pdf=pdf, Html=html, ContainedAnalysisRequests=uids, Metadata=metadata) # Commit the changes transaction.commit() logger.info("Create Report for {} [DONE]".format(parent_id)) return report
def decorator(*args, **kwargs): try: logger.info("==> {}::Acquire Semaphore ...".format(func.__name__)) semaphore.acquire() return func(*args, **kwargs) finally: logger.info("<== {}::Release Semaphore ...".format(func.__name__)) semaphore.release()
def wrapper(*args, **kwargs): start = time.time() return_value = func(*args, **kwargs) end = time.time() duration = float(end - start) if duration > threshold: logger.info("Execution of '{}{}' took {:2f}s".format( func.__name__, args, duration)) return return_value
def to_1000(portal_setup): """Initial version to 1000 :param portal_setup: The portal_setup tool """ logger.info("Run all import steps from SENAITE IMPRESS ...") portal_setup.runAllImportStepsFromProfile(PROFILE_ID) logger.info("Run all import steps from SENAITE IMPRESS [DONE]")
def to_latest(portal_setup): """Update to latest version :param portal_setup: The portal_setup tool """ logger.info("Run all import steps from SENAITE LIMS ...") context = portal_setup._getImportContext(PROFILE_ID) portal = context.getSite() # noqa portal_setup.runAllImportStepsFromProfile(PROFILE_ID) logger.info("Run all import steps from SENAITE LIMS [DONE]")
def to_1000(portal_setup): """Initial version to 1000 :param portal_setup: The portal_setup tool """ logger.info("Run all import steps from SENAITE LIMS ...") context = portal_setup._getImportContext(PROFILE_ID) portal = context.getSite() setup_html_filter(portal) portal_setup.runAllImportStepsFromProfile(PROFILE_ID) logger.info("Run all import steps from SENAITE LIMS [DONE]")
def post_install(portal_setup): """Runs after the last import step of the *default* profile This handler is registered as a *post_handler* in the generic setup profile :param portal_setup: SetupTool """ logger.info("SENAITE IMPRESS install handler [BEGIN]") # https://docs.plone.org/develop/addons/components/genericsetup.html#custom-installer-code-setuphandlers-py profile_id = "profile-senaite.impress:default" context = portal_setup._getImportContext(profile_id) portal = context.getSite() # noqa logger.info("SENAITE IMPRESS install handler [DONE]")
def create_report(self, parent, pdf, html, uids, metadata, csv_text=None): """Create a new report object NOTE: We limit the creation of reports to 1 to avoid conflict errors on simultaneous publication. :param parent: parent object where to create the report inside :returns: ARReport """ parent_id = api.get_id(parent) logger.info("Create Report for {} ...".format(parent_id)) # Manually update the view on the database to avoid conflict errors parent._p_jar.sync() query = { 'portal_type': 'ARReport', 'path': { 'query': api.get_path(parent), 'depth': 1 } } brains = api.search(query, 'portal_catalog') coa_num = '{}-COA-{}'.format(parent_id, len(brains) + 1) # Create the report object report = api.create(parent, "ARReport", AnalysisRequest=api.get_uid(parent), Pdf=pdf, Html=html, CSV=csv_text, ContainedAnalysisRequests=uids, Metadata=metadata) fld = report.getField('Pdf') fld.get(report).setFilename(coa_num + ".pdf") fld.get(report).setContentType('application/pdf') fld = report.getField('CSV') fld.get(report).setFilename(coa_num + ".csv") fld.get(report).setContentType('text/csv') # Commit the changes transaction.commit() logger.info("Create Report for {} [DONE]".format(parent_id)) return report
def ajax_get(self, uid, *args, **kwargs): """Return the JSONified data from the wrapped object Any additional positional parameter in *args will pick only these keys from the returned dictionary. """ logger.info("ajaxPrintView::ajax_get: {}{}".format( uid, "/".join(args))) model = SuperModel(uid) if not model.is_valid(): return self.fail("No object found for UID '{}'".format(uid), status=404) if args: return self.pick(model, *args) return model.to_dict()
def _layout_and_paginate(self, html): """Layout and paginate the given HTML into WeasyPrint `Document` objects http://weasyprint.readthedocs.io/en/stable/api.html#python-api """ # ensure we have plain html and not a BS4 node html = self.to_html(html) start = time.time() # Lay out and paginate the document html = HTML( string=html, url_fetcher=self.url_fetcher, base_url=self.base_url) document = html.render(stylesheets=self.css) end = time.time() logger.info("Publisher::Layout step took {:.2f}s for {} pages" .format(end-start, len(document.pages))) return document
def ajax_load_preview(self): """Recalculate the HTML of one rendered report after all the embedded JavaScripts modified the report on the client side. """ # Data sent via async ajax call as JSON data from the frontend data = self.get_json() # This is the html after it was rendered by the client browser and # eventually extended by JavaScript, e.g. Barcodes or Graphs added etc. # N.B. It might also contain multiple reports! html = data.get("html") if self.get_developer_mode(): return html # Metadata paperformat = data.get("format") orientation = data.get("orientation", "portrait") # Generate the print CSS with the set format/orientation css = self.get_print_css(paperformat=paperformat, orientation=orientation) logger.info(u"Preview CSS: {}".format(css)) # get an publisher instance publisher = self.publisher # add the generated CSS to the publisher publisher.add_inline_css(css) # HTML image previews preview = u"" # Generate PNG previews for the pages of each report for report_node in publisher.parse_reports(html): pages = publisher.write_png_pages(report_node) previews = map(lambda page: publisher.png_to_img(*page), pages) preview += "\n".join(previews) # Add the generated CSS to the preview, so that the container can grow # accordingly preview += "<style type='text/css'>{}</style>".format(css) return preview
def download(self): """Generate PDF and send it fot download """ form = self.request.form # This is the html after it was rendered by the client browser and # eventually extended by JavaScript, e.g. Barcodes or Graphs added etc. # NOTE: It might also contain multiple reports! html = form.get("html", "") # convert to unicode # https://github.com/senaite/senaite.impress/pull/93 html = api.safe_unicode(html) # get the selected template template = form.get("template") # get the selected paperformat paperformat = form.get("format") # get the selected orientation orientation = form.get("orientation", "portrait") # get the filename filename = form.get("filename", "{}.pdf".format(template)) # Generate the print CSS with the set format/orientation css = self.get_print_css(paperformat=paperformat, orientation=orientation) logger.info(u"Print CSS: {}".format(css)) # get the publisher instance publisher = self.publisher # add the generated CSS to the publisher publisher.add_inline_css(css) # generate the PDF pdf = publisher.write_pdf(html) self.request.response.setHeader( "Content-Disposition", "attachment; filename=%s.pdf" % filename) self.request.response.setHeader("Content-Type", "application/pdf") self.request.response.setHeader("Content-Length", len(pdf)) self.request.response.setHeader("Cache-Control", "no-store") self.request.response.setHeader("Pragma", "no-cache") self.request.response.write(pdf)
def url_fetcher(self, url): """Fetches internal URLs by path and not via an external request. N.B. Multiple calls to this method might exhaust the available threads of the server, which causes a hanging instance. """ if url.startswith("data"): logger.info("Data URL, delegate to default URL fetcher...") return default_url_fetcher(url) logger.info("Fetching URL '{}' for WeasyPrint".format(url)) # get the pyhsical path from the URL request = api.get_request() host = request.get_header("HOST") path = "/".join(request.physicalPathFromURL(url)) # fetch the object by sub-request portal = api.get_portal() context = portal.restrictedTraverse(path, None) if context is None or host not in url: logger.info("External URL, delegate to default URL fetcher...") return default_url_fetcher(url) logger.info("Local URL, fetching data by path '{}'".format(path)) # get the data via an authenticated subrequest response = subrequest(path) # Prepare the return data as required by WeasyPrint string = response.getBody() filename = url.split("/")[-1] mime_type = mimetypes.guess_type(url)[0] redirected_url = url return { "string": string, "filename": filename, "mime_type": mime_type, "redirected_url": redirected_url, }
def __init__(self, type="senaite.impress.reports"): logger.info("TemplateFinder::init:type={}".format(type)) self.type = type
def ajax_save_reports(self): """Render all reports as PDFs and store them as AR Reports """ # Data sent via async ajax call as JSON data from the frontend data = self.get_json() # This is the html after it was rendered by the client browser and # eventually extended by JavaScript, e.g. Barcodes or Graphs added etc. # N.B. It might also contain multiple reports! html = data.get("html") # Metadata paperformat = data.get("format") template = data.get("template") orientation = data.get("orientation", "portrait") timestamp = DateTime().ISO8601() is_multi_template = self.is_multi_template(template) store_individually = self.store_multireports_individually() # Generate the print CSS with the set format/orientation css = self.get_print_css(paperformat=paperformat, orientation=orientation) logger.info(u"Print CSS: {}".format(css)) # get an publisher instance publisher = self.publisher # add the generated CSS to the publisher publisher.add_inline_css(css) # TODO: Refactor code below to be not AR specific # remember the values of the last iteration for the exit url client_url = None report_uids = None for report_node in publisher.parse_reports(html): # generate the PDF pdf = publisher.write_pdf(report_node) # get contained AR UIDs in this report uids = filter(None, report_node.get("uids", "").split(",")) # get the AR objects objs = map(api.get_object_by_uid, uids) # sort the objects by created to have the most recent object first # -> supersedes https://github.com/senaite/senaite.impress/pull/48 objs = sorted(objs, key=methodcaller("created"), reverse=True) # remember generated report objects reports = [] for obj in objs: # TODO: refactor to adapter # Create a report object which holds the generated PDF title = "Report-{}".format(obj.getId()) report = api.create(obj, "ARReport", title=title) report.edit(AnalysisRequest=api.get_uid(obj), Pdf=pdf, Html=publisher.to_html(report_node), ContainedAnalysisRequests=uids, Metadata={ "template": template, "paperformat": paperformat, "orientation": orientation, "timestamp": timestamp, "contained_requests": uids, }) reports.append(report) client_url = api.get_url(obj.getClient()) # generate report only for the primary object if is_multi_template and not store_individually: break # remember the generated report UIDs for this iteration report_uids = map(api.get_uid, reports) # This is the clicked button name from the ReactJS component action = data.get("action", "save") exit_url = self.context.absolute_url() if all([client_url, report_uids]): endpoint = "reports_listing" if action == "email": endpoint = "email?uids={}".format(",".join(report_uids)) exit_url = "{}/{}".format(client_url, endpoint) return exit_url
def __init__(self, model, request): logger.info("SingleReportView::__init__:model={}".format(model)) super(SingleReportView, self).__init__(model, request) self.model = model self.request = request
def __init__(self, collection, request): logger.info( "MultiReportView::__init__:collection={}".format(collection)) super(MultiReportView, self).__init__(collection, request) self.collection = collection self.request = request
def ajax_save_reports(self): """Render all reports as PDFs and store them as AR Reports """ # Data sent via async ajax call as JSON data from the frontend data = self.get_json() # This is the html after it was rendered by the client browser and # eventually extended by JavaScript, e.g. Barcodes or Graphs added etc. # NOTE: It might also contain multiple reports! html = data.get("html") # get the triggered action (Save|Email) action = data.get("action", "save") # get the selected template template = data.get("template") # get the selected paperformat paperformat = data.get("format") # get the selected orientation orientation = data.get("orientation", "portrait") # Generate the print CSS with the set format/orientation css = self.get_print_css(paperformat=paperformat, orientation=orientation) logger.info(u"Print CSS: {}".format(css)) # get the publisher instance publisher = self.publisher # add the generated CSS to the publisher publisher.add_inline_css(css) # split the html per report # NOTE: each report is an instance of <bs4.Tag> html_reports = publisher.parse_reports(html) # generate a PDF for each HTML report pdf_reports = map(publisher.write_pdf, html_reports) # extract the UIDs of each HTML report # NOTE: UIDs are injected in `.analysisrequest.reportview.render` report_uids = map(lambda report: report.get("uids", "").split(","), html_reports) # prepare some metadata metadata = { "template": template, "paperformat": paperformat, "orientation": orientation, "timestamp": DateTime().ISO8601(), } # get the storage multi-adapter to save the generated PDFs storage = getMultiAdapter((self.context, self.request), IPdfReportStorage) report_groups = [] for pdf, html, uids in zip(pdf_reports, html_reports, report_uids): # ensure we have valid UIDs here uids = filter(api.is_uid, uids) # convert the bs4.Tag back to pure HTML html = publisher.to_html(html) # BBB: inject contained UIDs into metadata metadata["contained_requests"] = uids # store the report(s) objs = storage.store(pdf, html, uids, metadata=metadata) # append the generated reports to the list report_groups.append(objs) # NOTE: The reports might be stored in multiple places (clients), # which makes it difficult to redirect to a single exit URL # based on the action the users clicked (save/email) exit_urls = map( lambda reports: self.get_exit_url_for(reports, action=action), report_groups) if not exit_urls: return api.get_url(self.context) return exit_urls[0]