Пример #1
0
    def get_asset_content(self, url, url_info=None, custom_attachments=None):
        """
        Fetch the content of an asset (scss / js) file. That content is either
        the one of the related file on the disk or the one of the corresponding
        custom ir.attachment record.

        Params:
            url (str): the URL of the asset (scss / js) file/ir.attachment

            url_info (dict, optional):
                the related url info (see get_asset_info) (allows to optimize
                some code which already have the info and do not want this
                function to re-get it)

            custom_attachments (ir.attachment(), optional):
                the related custom ir.attachment records the function might need
                to search into (allows to optimize some code which already have
                that info and do not want this function to re-get it)

        Returns:
            utf-8 encoded content of the asset (scss / js)
        """
        if url_info is None:
            url_info = self.get_asset_info(url)

        if url_info["customized"]:
            # If the file is already customized, the content is found in the
            # corresponding attachment
            attachment = None
            if custom_attachments is None:
                attachment = self._get_custom_attachment(url)
            else:
                attachment = custom_attachments.filtered(
                    lambda r: r.url == url)
            return attachment and base64.b64decode(attachment.datas) or False

        # If the file is not yet customized, the content is found by reading
        # the local scss file
        module = url_info["module"]
        module_path = get_module_path(module)
        module_resource_path = get_resource_path(module,
                                                 url_info["resource_path"])
        if module_path and module_resource_path:
            module_path = os.path.join(
                os.path.normpath(module_path),
                '')  # join ensures the path ends with '/'
            module_resource_path = os.path.normpath(module_resource_path)
            if module_resource_path.startswith(module_path):
                with open(module_resource_path, "rb") as f:
                    return f.read()
Пример #2
0
 def get_content(self, url, xmlid):
     custom_url = self._get_custom_url(url, xmlid)
     custom_attachment = self._get_custom_attachment(custom_url)
     if custom_attachment.exists():
         return base64.b64decode(custom_attachment.datas).decode('utf-8')
     else:
         match = re.compile("^/(\w+)/(.+?)(\.custom\.(.+))?\.(\w+)$").match(
             url)
         module_path = module.get_module_path(match.group(1))
         resource_path = "%s.%s" % (match.group(2), match.group(5))
         module_resource_path = module.get_resource_path(
             module_path, resource_path)
         with open(module_resource_path, "rb") as file:
             return file.read().decode('utf-8')
Пример #3
0
    def _binary_ir_attachment_redirect_content(
            cls, record, default_mimetype='application/octet-stream'):
        # mainly used for theme images attachemnts
        status = content = filename = filehash = None
        mimetype = getattr(record, 'mimetype', False)
        if record.type == 'url' and record.url:
            # if url in in the form /somehint server locally
            url_match = re.match("^/(\w+)/(.+)$", record.url)
            if url_match:
                module = url_match.group(1)
                module_path = get_module_path(module)
                module_resource_path = get_resource_path(
                    module, url_match.group(2))

                if module_path and module_resource_path:
                    module_path = os.path.join(
                        os.path.normpath(module_path),
                        '')  # join ensures the path ends with '/'
                    module_resource_path = os.path.normpath(
                        module_resource_path)
                    if module_resource_path.startswith(module_path):
                        with open(module_resource_path, 'rb') as f:
                            content = base64.b64encode(f.read())
                        status = 200
                        filename = os.path.basename(module_resource_path)
                        mimetype = guess_mimetype(base64.b64decode(content),
                                                  default=default_mimetype)
                        filehash = '"%s"' % hashlib.md5(
                            pycompat.to_text(content).encode(
                                'utf-8')).hexdigest()

            if not content:
                status = 301
                content = record.url

        return status, content, filename, mimetype, filehash
Пример #4
0
    def binary_content(cls,
                       xmlid=None,
                       model='ir.attachment',
                       id=None,
                       field='datas',
                       unique=False,
                       filename=None,
                       filename_field='datas_fname',
                       download=False,
                       mimetype=None,
                       default_mimetype='application/octet-stream',
                       access_token=None,
                       env=None):
        """ Get file, attachment or downloadable content

        If the ``xmlid`` and ``id`` parameter is omitted, fetches the default value for the
        binary field (via ``default_get``), otherwise fetches the field for
        that precise record.

        :param str xmlid: xmlid of the record
        :param str model: name of the model to fetch the binary from
        :param int id: id of the record from which to fetch the binary
        :param str field: binary field
        :param bool unique: add a max-age for the cache control
        :param str filename: choose a filename
        :param str filename_field: if not create an filename with model-id-field
        :param bool download: apply headers to download the file
        :param str mimetype: mintype of the field (for headers)
        :param str default_mimetype: default mintype if no mintype found
        :param str access_token: optional token for unauthenticated access
                                 only available  for ir.attachment
        :param Environment env: by default use request.env
        :returns: (status, headers, content)
        """
        env = env or request.env
        # get object and content
        obj = None
        if xmlid:
            obj = env.ref(xmlid, False)
        elif id and model == 'ir.attachment' and access_token:
            obj = env[model].sudo().browse(int(id))
            if not consteq(obj.access_token, access_token):
                return (403, [], None)
        elif id and model in env.registry:
            obj = env[model].browse(int(id))

        # obj exists
        if not obj or not obj.exists() or field not in obj:
            return (404, [], None)

        # check read access
        try:
            last_update = obj['__last_update']
        except AccessError:
            return (403, [], None)

        status, headers, content = None, [], None

        # attachment by url check
        module_resource_path = None
        if model == 'ir.attachment' and obj.type == 'url' and obj.url:
            url_match = re.match("^/(\w+)/(.+)$", obj.url)
            if url_match:
                module = url_match.group(1)
                module_path = get_module_path(module)
                module_resource_path = get_resource_path(
                    module, url_match.group(2))
                if module_path and module_resource_path:
                    module_path = os.path.join(
                        os.path.normpath(module_path),
                        '')  # join ensures the path ends with '/'
                    module_resource_path = os.path.normpath(
                        module_resource_path)
                    if module_resource_path.startswith(module_path):
                        with open(module_resource_path, 'rb') as f:
                            content = base64.b64encode(f.read())
                        last_update = pycompat.text_type(
                            os.path.getmtime(module_resource_path))

            if not module_resource_path:
                module_resource_path = obj.url

            if not content:
                status = 301
                content = module_resource_path
        else:
            content = obj[field] or ''

        # filename
        if not filename:
            if filename_field in obj:
                filename = obj[filename_field]
            elif module_resource_path:
                filename = os.path.basename(module_resource_path)
            else:
                filename = "%s-%s-%s" % (obj._name, obj.id, field)

        # mimetype
        mimetype = 'mimetype' in obj and obj.mimetype or False
        if not mimetype:
            if filename:
                mimetype = mimetypes.guess_type(filename)[0]
            if not mimetype and getattr(env[model]._fields[field],
                                        'attachment', False):
                # for binary fields, fetch the ir_attachement for mimetype check
                attach_mimetype = env['ir.attachment'].search_read(
                    domain=[('res_model', '=', model), ('res_id', '=', id),
                            ('res_field', '=', field)],
                    fields=['mimetype'],
                    limit=1)
                mimetype = attach_mimetype and attach_mimetype[0]['mimetype']
            if not mimetype:
                mimetype = guess_mimetype(base64.b64decode(content),
                                          default=default_mimetype)

        headers += [('Content-Type', mimetype),
                    ('X-Content-Type-Options', 'nosniff')]

        # cache
        etag = bool(request) and request.httprequest.headers.get(
            'If-None-Match')
        retag = '"%s"' % hashlib.md5(
            pycompat.to_text(content).encode('utf-8')).hexdigest()
        status = status or (304 if etag == retag else 200)
        headers.append(('ETag', retag))
        headers.append(
            ('Cache-Control', 'max-age=%s' % (STATIC_CACHE if unique else 0)))

        # content-disposition default name
        if download:
            headers.append(
                ('Content-Disposition', cls.content_disposition(filename)))
        return (status, headers, content)
Пример #5
0
    def get_assets_editor_resources(self, key, get_views=True, get_less=True, bundles=False, bundles_restriction=[]):
        # Related views must be fetched if the user wants the views and/or the style
        views = request.env["ir.ui.view"].get_related_views(key, bundles=bundles)
        views = views.read(['name', 'id', 'key', 'xml_id', 'arch', 'active', 'inherit_id'])

        less_files_data_by_bundle = []

        # Load less only if asked by the user
        if get_less:
            # Compile regex outside of the loop
            # This will used to exclude library less files from the result
            excluded_url_matcher = re.compile("^(.+/lib/.+)|(.+import_bootstrap.less)$")

            # Load already customized less files attachments
            custom_attachments = request.env["ir.attachment"].search([("url", "=like", self._make_custom_less_file_url("%%.%%", "%%"))])

            # First check the t-call-assets used in the related views
            url_infos = dict()
            for v in views:
                for asset_call_node in etree.fromstring(v["arch"]).xpath("//t[@t-call-assets]"):
                    if asset_call_node.get("t-css") == "false":
                        continue
                    asset_name = asset_call_node.get("t-call-assets")

                    # Loop through bundle files to search for LESS file info
                    less_files_data = []
                    for file_info in request.env["ir.qweb"]._get_asset_content(asset_name, {})[0]:
                        if file_info["atype"] != "text/less":
                            continue
                        url = file_info["url"]

                        # Exclude library files (see regex above)
                        if excluded_url_matcher.match(url):
                            continue

                        # Check if the file is customized and get bundle/path info
                        less_file_data = self._match_less_file_url(url)
                        if not less_file_data:
                            continue

                        # Save info (arch will be fetched later)
                        url_infos[url] = less_file_data
                        less_files_data.append(url)

                    # Less data is returned sorted by bundle, with the bundles names and xmlids
                    if len(less_files_data):
                        less_files_data_by_bundle.append([dict(xmlid=asset_name, name=request.env.ref(asset_name).name), less_files_data])

            # Filter bundles/files:
            # - A file which appears in multiple bundles only appears in the first one (the first in the DOM)
            # - Only keep bundles with files which appears in the asked bundles and only keep those files
            for i in range(0, len(less_files_data_by_bundle)):
                bundle_1 = less_files_data_by_bundle[i]
                for j in range(0, len(less_files_data_by_bundle)):
                    bundle_2 = less_files_data_by_bundle[j]
                    # In unwanted bundles, keep only the files which are in wanted bundles too (less_helpers)
                    if bundle_1[0]["xmlid"] not in bundles_restriction and bundle_2[0]["xmlid"] in bundles_restriction:
                        bundle_1[1] = [item_1 for item_1 in bundle_1[1] if item_1 in bundle_2[1]]
            for i in range(0, len(less_files_data_by_bundle)):
                bundle_1 = less_files_data_by_bundle[i]
                for j in range(i+1, len(less_files_data_by_bundle)):
                    bundle_2 = less_files_data_by_bundle[j]
                    # In every bundle, keep only the files which were not found in previous bundles
                    bundle_2[1] = [item_2 for item_2 in bundle_2[1] if item_2 not in bundle_1[1]]

            # Only keep bundles which still have files and that were requested
            less_files_data_by_bundle = [
                data for data in less_files_data_by_bundle
                if (len(data[1]) > 0 and (not bundles_restriction or data[0]["xmlid"] in bundles_restriction))
            ]

            # Fetch the arch of each kept file, in each bundle
            for bundle_data in less_files_data_by_bundle:
                for i in range(0, len(bundle_data[1])):
                    url = bundle_data[1][i]
                    url_info = url_infos[url]

                    content = None
                    if url_info["customized"]:
                        # If the file is already customized, the content is found in the corresponding attachment
                        content = base64.b64decode(custom_attachments.filtered(lambda a: a.url == url).datas)
                    else:
                        # If the file is not yet customized, the content is found by reading the local less file
                        module = url_info["module"]
                        module_path = get_module_path(module)
                        module_resource_path = get_resource_path(module, url_info["resource_path"])
                        if module_path and module_resource_path:
                            module_path = os.path.join(os.path.normpath(module_path), '') # join ensures the path ends with '/'
                            module_resource_path = os.path.normpath(module_resource_path)
                            if module_resource_path.startswith(module_path):
                                with open(module_resource_path, "rb") as f:
                                    content = f.read()

                    bundle_data[1][i] = dict(
                        url = "/%s/%s" % (url_info["module"], url_info["resource_path"]),
                        arch = content,
                        customized = url_info["customized"],
                    )

        return dict(
            views = get_views and views or [],
            less = get_less and less_files_data_by_bundle or [],
        )