예제 #1
0
    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
예제 #2
0
 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()
예제 #3
0
 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
예제 #4
0
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]")
예제 #5
0
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]")
예제 #6
0
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]")
예제 #7
0
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]")
예제 #8
0
    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
예제 #9
0
    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()
예제 #10
0
    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
예제 #11
0
    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
예제 #12
0
    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)
예제 #13
0
    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,
        }
예제 #14
0
 def __init__(self, type="senaite.impress.reports"):
     logger.info("TemplateFinder::init:type={}".format(type))
     self.type = type
예제 #15
0
    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
예제 #16
0
 def __init__(self, model, request):
     logger.info("SingleReportView::__init__:model={}".format(model))
     super(SingleReportView, self).__init__(model, request)
     self.model = model
     self.request = request
예제 #17
0
 def __init__(self, collection, request):
     logger.info(
         "MultiReportView::__init__:collection={}".format(collection))
     super(MultiReportView, self).__init__(collection, request)
     self.collection = collection
     self.request = request
예제 #18
0
    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]