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()
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')
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
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)
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 [], )