def url_for(path_or_uri, lang=None): current_path = request.httprequest.path # should already be text location = pycompat.to_text(path_or_uri).strip() force_lang = lang is not None url = werkzeug.urls.url_parse(location) if not url.netloc and not url.scheme and (url.path or force_lang): location = werkzeug.urls.url_join(current_path, location) lang = pycompat.to_text(lang or request.context.get('lang') or 'en_US') langs = [lg[0] for lg in request.env['ir.http']._get_language_codes()] if (len(langs) > 1 or force_lang) and is_multilang_url(location, langs): ps = location.split(u'/') if ps[1] in langs: # Replace the language only if we explicitly provide a language to url_for if force_lang: ps[1] = lang # Remove the default language unless it's explicitly provided elif ps[1] == request.env['ir.http']._get_default_lang().code: ps.pop(1) # Insert the context language or the provided language elif lang != request.env['ir.http']._get_default_lang().code or force_lang: ps.insert(1, lang) location = u'/'.join(ps) return location
def _compile_directive_snippet(self, el, options): el.set('t-call', el.attrib.pop('t-snippet')) View = self.env['ir.ui.view'] view_id = View.get_view_id(el.attrib.get('t-call')) name = View.browse(view_id).name thumbnail = el.attrib.pop('t-thumbnail', "oe-thumbnail") div = u'<div name="%s" data-oe-type="snippet" data-oe-thumbnail="%s">' % ( escape(pycompat.to_text(name)), escape(pycompat.to_text(thumbnail)) ) return [self._append(ast.Str(div))] + self._compile_node(el, options) + [self._append(ast.Str(u'</div>'))]
def to_html(self): tagName, attributes, content = self.to_node() html = u"<%s " % tagName for name, value in attributes.items(): if value or isinstance(value, string_types): html += u' %s="%s"' % (name, escape(to_text(value))) if content is None: html += u'/>' else: html += u'>%s</%s>' % (escape(to_text(content)), tagName) return html
def from_data(self, fields, rows): if len(rows) > 65535: raise UserError( _('There are too many rows (%s rows, limit: 65535) to export as Excel 97-2003 (.xls) format. Consider splitting the export.' ) % len(rows)) workbook = xlwt.Workbook() worksheet = workbook.add_sheet('Sheet 1') for i, fieldname in enumerate(fields): worksheet.write(0, i, fieldname) worksheet.col(i).width = 8000 # around 220 pixels base_style = xlwt.easyxf('align: wrap yes') date_style = xlwt.easyxf('align: wrap yes', num_format_str='YYYY-MM-DD') datetime_style = xlwt.easyxf('align: wrap yes', num_format_str='YYYY-MM-DD HH:mm:SS') for row_index, row in enumerate(rows): for cell_index, cell_value in enumerate(row): cell_style = base_style if isinstance(cell_value, bytes) and not isinstance( cell_value, pycompat.string_types): # because xls uses raw export, we can get a bytes object # here. xlwt does not support bytes values in Python 3 -> # assume this is base64 and decode to a string, if this # fails note that you can't export try: cell_value = pycompat.to_text(cell_value) except UnicodeDecodeError: raise UserError( _("Binary fields can not be exported to Excel unless their content is base64-encoded. That does not seem to be the case for %s." ) % fields[cell_index]) if isinstance(cell_value, pycompat.string_types): cell_value = re.sub("\r", " ", pycompat.to_text(cell_value)) # Excel supports a maximum of 32767 characters in each cell: cell_value = cell_value[:32767] elif isinstance(cell_value, datetime.datetime): cell_style = datetime_style elif isinstance(cell_value, datetime.date): cell_style = date_style worksheet.write(row_index + 1, cell_index, cell_value, cell_style) fp = io.BytesIO() workbook.save(fp) fp.seek(0) data = fp.read() fp.close() return data
def to_node(self): if self.url: attr = OrderedDict([ ["type", "text/css"], ["rel", "stylesheet"], ["href", self.html_url], ["media", escape(to_text(self.media)) if self.media else None] ]) return ("link", attr, None) else: attr = OrderedDict([[ "type", "text/css" ], ["media", escape(to_text(self.media)) if self.media else None]]) return ("style", attr, self.with_header())
def _compile_directive_install(self, el, options): if self.user_has_groups('base.group_system'): module = self.env['ir.module.module'].search([('name', '=', el.attrib.get('t-install'))]) if not module or module.state == 'installed': return [] name = el.attrib.get('string') or 'Snippet' thumbnail = el.attrib.pop('t-thumbnail', 'oe-thumbnail') div = u'<div name="%s" data-oe-type="snippet" data-module-id="%s" data-oe-thumbnail="%s"><section/></div>' % ( escape(pycompat.to_text(name)), module.id, escape(pycompat.to_text(thumbnail)) ) return [self._append(ast.Str(div))] else: return []
def value_to_html(self, value, options): """ value_to_html(value, field, options=None) Converts a single value to its HTML version/output :rtype: unicode """ return html_escape(pycompat.to_text(value), options)
def value_to_html(self, value, options): if 'decimal_precision' in options: precision = self.env['decimal.precision'].search([ ('name', '=', options['decimal_precision']) ]).digits else: precision = options['precision'] if precision is None: fmt = '%f' else: value = float_utils.float_round(value, precision_digits=precision) fmt = '%.{precision}f'.format(precision=precision) formatted = self.user_lang().format(fmt, value, grouping=True).replace( r'-', u'-\N{ZERO WIDTH NO-BREAK SPACE}') # %f does not strip trailing zeroes. %g does but its precision causes # it to switch to scientific notation starting at a million *and* to # strip decimals. So use %f and if no precision was specified manually # strip trailing 0. if precision is None: formatted = re.sub(r'(?:(0|\d+?)0+)$', r'\1', formatted) return pycompat.to_text(formatted)
def encode_rfc2822_address_header(header_text): """If ``header_text`` contains non-ASCII characters, attempts to locate patterns of the form ``"Name" <address@domain>`` and replace the ``"Name"`` portion by the RFC2047-encoded version, preserving the address part untouched. """ def encode_addr(addr): name, email = addr # If s is a <text string>, then charset is a hint specifying the # character set of the characters in the string. The Unicode string # will be encoded using the following charsets in order: us-ascii, # the charset hint, utf-8. The first character set to not provoke a # UnicodeError is used. # -> always pass a text string to Header # also Header.__str__ in Python 3 "Returns an approximation of the # Header as a string, using an unlimited line length.", the old one # was "A synonym for Header.encode()." so call encode() directly? name = Header(pycompat.to_text(name)).encode() # if the from does not follow the (name <addr>),* convention, we might # try to encode meaningless strings as address, as getaddresses is naive # note it would also fail on real addresses with non-ascii characters try: return formataddr((name, email)) except UnicodeEncodeError: _logger.warning( _('Failed to encode the address %s\n' 'from mail header:\n%s') % (addr, header_text)) return "" addresses = getaddresses([pycompat.to_text(ustr(header_text))]) return COMMASPACE.join(a for a in (encode_addr(addr) for addr in addresses) if a)
def value_to_html(self, value, options): if not value: return '' lang = self.user_lang() locale = babel.Locale.parse(lang.code) if isinstance(value, pycompat.string_types): value = fields.Datetime.from_string(value) value = fields.Datetime.context_timestamp(self, value) if options and 'format' in options: pattern = options['format'] else: if options and options.get('time_only'): strftime_pattern = (u"%s" % (lang.time_format)) else: strftime_pattern = (u"%s %s" % (lang.date_format, lang.time_format)) pattern = posix_to_ldml(strftime_pattern, locale=locale) if options and options.get('hide_seconds'): pattern = pattern.replace(":ss", "").replace(":s", "") return pycompat.to_text( babel.dates.format_datetime(value, format=pattern, locale=locale))
def authorize(env, key, account_token, credit, dbuuid=False, description=None, credit_template=None): endpoint = get_endpoint(env) params = { 'account_token': account_token, 'credit': credit, 'key': key, 'description': description, } if dbuuid: params.update({'dbuuid': dbuuid}) try: transaction_token = jsonrpc(endpoint + '/iap/1/authorize', params=params) except InsufficientCreditError as e: if credit_template: arguments = json.loads(e.args[0]) arguments['body'] = pycompat.to_text( env['ir.qweb'].render(credit_template)) e.args = (json.dumps(arguments), ) raise e return transaction_token
def nl2br(string): """ Converts newlines to HTML linebreaks in ``string``. returns the unicode result :param str string: :rtype: unicode """ return pycompat.to_text(string).replace(u'\n', u'<br>\n')
def _binary_record_content(self, record, field='datas', filename=None, filename_field='name', default_mimetype='application/octet-stream'): model = record._name mimetype = 'mimetype' in record and record.mimetype or False content = None filehash = 'checksum' in record and record['checksum'] or False field_def = record._fields[field] if field_def.type == 'binary' and field_def.attachment: field_attachment = self.env['ir.attachment'].sudo().search_read( domain=[('res_model', '=', model), ('res_id', '=', record.id), ('res_field', '=', field)], fields=['datas', 'mimetype', 'checksum'], limit=1) if field_attachment: mimetype = field_attachment[0]['mimetype'] content = field_attachment[0]['datas'] filehash = field_attachment[0]['checksum'] if not content: content = record[field] or '' # filename default_filename = False if not filename: if filename_field in record: filename = record[filename_field] if not filename: default_filename = True filename = "%s-%s-%s" % (record._name, record.id, field) if not mimetype: try: decoded_content = base64.b64decode(content) except base64.binascii.Error: # if we could not decode it, no need to pass it down: it would crash elsewhere... return (404, [], None) mimetype = guess_mimetype(decoded_content, default=default_mimetype) # extension _, existing_extension = os.path.splitext(filename) if not existing_extension or default_filename: extension = mimetypes.guess_extension(mimetype) if extension: filename = "%s%s" % (filename, extension) if not filehash: filehash = '"%s"' % hashlib.md5( pycompat.to_text(content).encode('utf-8')).hexdigest() status = 200 if content else 404 return status, content, filename, mimetype, filehash
def to_html(self, sep=None, css=True, js=True, debug=False, async_load=False, url_for=(lambda url: url)): nodes = self.to_node(css=css, js=js, debug=debug, async_load=async_load) if sep is None: sep = u'\n ' response = [] for tagName, attributes, content in nodes: html = u"<%s " % tagName for name, value in attributes.items(): if value or isinstance(value, string_types): html += u' %s="%s"' % (name, escape(to_text(value))) if content is None: html += u'/>' else: html += u'>%s</%s>' % (escape(to_text(content)), tagName) response.append(html) return sep + sep.join(response)
def fiscal_pos_map_to_csv(self): writer = pycompat.csv_writer( open( 'account.fiscal.' 'position.tax.template-%s.csv' % self.suffix, 'wb')) fiscal_pos_map_iterator = self.iter_fiscal_pos_map() keys = next(fiscal_pos_map_iterator) writer.writerow(keys) for row in fiscal_pos_map_iterator: writer.writerow([pycompat.to_text(s) for s in row.values()])
def _query(self, conf, filter, retrieve_attributes=None): """ Query an LDAP server with the filter argument and scope subtree. Allow for all authentication methods of the simple authentication method: - authenticated bind (non-empty binddn + valid password) - anonymous bind (empty binddn + empty password) - unauthenticated authentication (non-empty binddn + empty password) .. seealso:: :rfc:`4513#section-5.1` - LDAP: Simple Authentication Method. :param dict conf: LDAP configuration :param filter: valid LDAP filter :param list retrieve_attributes: LDAP attributes to be retrieved. \ If not specified, return all attributes. :return: ldap entries :rtype: list of tuples (dn, attrs) """ results = [] try: conn = self._connect(conf) ldap_password = conf['ldap_password'] or '' ldap_binddn = conf['ldap_binddn'] or '' conn.simple_bind_s(to_text(ldap_binddn), to_text(ldap_password)) results = conn.search_st(to_text(conf['ldap_base']), ldap.SCOPE_SUBTREE, filter, retrieve_attributes, timeout=60) conn.unbind() except ldap.INVALID_CREDENTIALS: _logger.error('LDAP bind failed.') except ldap.LDAPError as e: _logger.error('An LDAP exception occurred: %s', e) return results
def value_to_html(self, value, options): locale = babel.Locale.parse(self.user_lang().code) if isinstance(value, str): value = fields.Datetime.from_string(value) # value should be a naive datetime in UTC. So is fields.Datetime.now() reference = fields.Datetime.from_string(options['now']) return pycompat.to_text( babel.dates.format_timedelta(value - reference, add_direction=True, locale=locale))
def record_to_html(self, record, field_name, options): if not getattr(record, field_name): return None view = getattr(record, field_name) if view._name != "ir.ui.view": _logger.warning("%s.%s must be a 'ir.ui.view' model." % (record, field_name)) return None return pycompat.to_text( view.render(options.get('values', {}), engine='ir.qweb'))
def _adyen_form_get_tx_from_data(self, data): reference, pspReference = data.get('merchantReference'), data.get( 'pspReference') if not reference or not pspReference: error_msg = _( 'Adyen: received data with missing reference (%s) or missing pspReference (%s)' ) % (reference, pspReference) _logger.info(error_msg) raise ValidationError(error_msg) # find tx -> @TDENOTE use pspReference ? tx = self.env['payment.transaction'].search([('reference', '=', reference)]) if not tx or len(tx) > 1: error_msg = _('Adyen: received data for reference %s') % ( reference) if not tx: error_msg += _('; no order found') else: error_msg += _('; multiple order found') _logger.info(error_msg) raise ValidationError(error_msg) # verify shasign if len(tx.acquirer_id.adyen_skin_hmac_key) == 64: shasign_check = tx.acquirer_id._adyen_generate_merchant_sig_sha256( 'out', data) else: shasign_check = tx.acquirer_id._adyen_generate_merchant_sig( 'out', data) if to_text(shasign_check) != to_text(data.get('merchantSig')): error_msg = _( 'Adyen: invalid merchantSig, received %s, computed %s') % ( data.get('merchantSig'), shasign_check) _logger.warning(error_msg) raise ValidationError(error_msg) return tx
def url_lang(path_or_uri, lang_code=None): ''' Given a relative URL, make it absolute and add the required lang or remove useless lang. Nothing will be done for absolute URL. If there is only one language installed, the lang will not be handled unless forced with `lang` parameter. :param lang_code: Must be the lang `code`. It could also be something else, such as `'[lang]'` (used for url_return). ''' Lang = request.env['res.lang'] location = pycompat.to_text(path_or_uri).strip() force_lang = lang_code is not None url = werkzeug.urls.url_parse(location) # relative URL with either a path or a force_lang if not url.netloc and not url.scheme and (url.path or force_lang): location = werkzeug.urls.url_join(request.httprequest.path, location) lang_url_codes = [url_code for _, url_code, _ in Lang.get_available()] lang_code = pycompat.to_text(lang_code or request.context['lang']) lang_url_code = Lang._lang_get(lang_code).url_code lang_url_code = lang_url_code if lang_url_code in lang_url_codes else lang_code if (len(lang_url_codes) > 1 or force_lang) and is_multilang_url( location, lang_url_codes): ps = location.split(u'/') default_lg = request.env['ir.http']._get_default_lang() if ps[1] in lang_url_codes: # Replace the language only if we explicitly provide a language to url_for if force_lang: ps[1] = lang_url_code # Remove the default language unless it's explicitly provided elif ps[1] == default_lg.url_code: ps.pop(1) # Insert the context language or the provided language elif lang_url_code != default_lg.url_code or force_lang: ps.insert(1, lang_url_code) location = u'/'.join(ps) return location
def from_data(self, fields, rows): fp = io.BytesIO() writer = pycompat.csv_writer(fp, quoting=1) writer.writerow(fields) for data in rows: row = [] for d in data: # Spreadsheet apps tend to detect formulas on leading =, + and - if isinstance(d, pycompat.string_types) and d.startswith( ('=', '-', '+')): d = "'" + d row.append(pycompat.to_text(d)) writer.writerow(row) return fp.getvalue()
def taxes_to_csv(self): writer = pycompat.csv_writer( open('account.tax.template-%s.csv' % self.suffix, 'wb')) taxes_iterator = self.iter_taxes() keys = next(taxes_iterator) writer.writerow(keys[3:] + ['sequence']) seq = 100 for row in sorted(taxes_iterator, key=lambda r: r['description']): if not _is_true(row['active']): continue seq += 1 if row['parent_id:id']: cur_seq = seq + 1000 else: cur_seq = seq writer.writerow( [pycompat.to_text(v) for v in list(row.values())[3:]] + [cur_seq])
def encode_header(header_text): """Returns an appropriate representation of the given header value, suitable for direct assignment as a header value in an email.message.Message. RFC2822 assumes that headers contain only 7-bit characters, so we ensure it is the case, using RFC2047 encoding when needed. :param header_text: unicode or utf-8 encoded string with header value :rtype: string | email.header.Header :return: if ``header_text`` represents a plain ASCII string, return the same 7-bit string, otherwise returns an email.header.Header that will perform the appropriate RFC2047 encoding of non-ASCII values. """ if not header_text: return "" header_text = ustr(header_text) # FIXME: require unicode higher up? if is_ascii(header_text): return pycompat.to_text(header_text) return Header(header_text, 'utf-8')
def _authenticate(self, conf, login, password): """ Authenticate a user against the specified LDAP server. In order to prevent an unintended 'unauthenticated authentication', which is an anonymous bind with a valid dn and a blank password, check for empty passwords explicitely (:rfc:`4513#section-6.3.1`) :param dict conf: LDAP configuration :param login: username :param password: Password for the LDAP user :return: LDAP entry of authenticated user or False :rtype: dictionary of attributes """ if not password: return False entry = False try: filter = filter_format(conf['ldap_filter'], (login, )) except TypeError: _logger.warning( 'Could not format LDAP filter. Your filter should contain one \'%s\'.' ) return False try: results = self._query(conf, tools.ustr(filter)) # Get rid of (None, attrs) for searchResultReference replies results = [i for i in results if i[0]] if len(results) == 1: dn = results[0][0] conn = self._connect(conf) conn.simple_bind_s(dn, to_text(password)) conn.unbind() entry = results[0] except ldap.INVALID_CREDENTIALS: return False except ldap.LDAPError as e: _logger.error('An LDAP exception occurred: %s', e) return entry
def encode_header_param(param_text): """Returns an appropriate RFC2047 encoded representation of the given header parameter value, suitable for direct assignation as the param value (e.g. via Message.set_param() or Message.add_header()) RFC2822 assumes that headers contain only 7-bit characters, so we ensure it is the case, using RFC2047 encoding when needed. :param param_text: unicode or utf-8 encoded string with header value :rtype: string :return: if ``param_text`` represents a plain ASCII string, return the same 7-bit string, otherwise returns an ASCII string containing the RFC2047 encoded text. """ # For details see the encode_header() method that uses the same logic if not param_text: return "" param_text = ustr(param_text) # FIXME: require unicode higher up? if is_ascii(param_text): return pycompat.to_text( param_text) # TODO: is that actually necessary? return Charset("utf-8").header_encode(param_text)
def encode_addr(addr): name, email = addr # If s is a <text string>, then charset is a hint specifying the # character set of the characters in the string. The Unicode string # will be encoded using the following charsets in order: us-ascii, # the charset hint, utf-8. The first character set to not provoke a # UnicodeError is used. # -> always pass a text string to Header # also Header.__str__ in Python 3 "Returns an approximation of the # Header as a string, using an unlimited line length.", the old one # was "A synonym for Header.encode()." so call encode() directly? name = Header(pycompat.to_text(name)).encode() # if the from does not follow the (name <addr>),* convention, we might # try to encode meaningless strings as address, as getaddresses is naive # note it would also fail on real addresses with non-ascii characters try: return formataddr((name, email)) except UnicodeEncodeError: _logger.warning( _('Failed to encode the address %s\n' 'from mail header:\n%s') % addr, header_text) return ""
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 tax_codes_to_csv(self): writer = pycompat.csv_writer( open('account.tax.code.template-%s.csv' % self.suffix, 'wb')) tax_codes_iterator = self.iter_tax_codes() keys = next(tax_codes_iterator) writer.writerow(keys) # write structure tax codes tax_codes = {} # code: id for row in tax_codes_iterator: tax_code = row['code'] if tax_code in tax_codes: raise RuntimeError('duplicate tax code %s' % tax_code) tax_codes[tax_code] = row['id'] writer.writerow([pycompat.to_text(v) for v in row.values()]) # read taxes and add leaf tax codes new_tax_codes = {} # id: parent_code def add_new_tax_code(tax_code_id, new_name, new_parent_code): if not tax_code_id: return name, parent_code = new_tax_codes.get(tax_code_id, (None, None)) if parent_code and parent_code != new_parent_code: raise RuntimeError('tax code "%s" already exist with ' 'parent %s while trying to add it with ' 'parent %s' % (tax_code_id, parent_code, new_parent_code)) else: new_tax_codes[tax_code_id] = (new_name, new_parent_code) taxes_iterator = self.iter_taxes() next(taxes_iterator) for row in taxes_iterator: if not _is_true(row['active']): continue if row['child_depend'] and row['amount'] != 1: raise RuntimeError('amount must be one if child_depend ' 'for %s' % row['id']) # base parent base_code = row['BASE_CODE'] if not base_code or base_code == '/': base_code = 'NA' if base_code not in tax_codes: raise RuntimeError('undefined tax code %s' % base_code) if base_code != 'NA': if row['child_depend']: raise RuntimeError('base code specified ' 'with child_depend for %s' % row['id']) if not row['child_depend']: # ... in lux, we have the same code for invoice and refund if base_code != 'NA': assert row[ 'base_code_id:id'], 'missing base_code_id for %s' % row[ 'id'] assert row['ref_base_code_id:id'] == row['base_code_id:id'] add_new_tax_code(row['base_code_id:id'], 'Base - ' + row['name'], base_code) # tax parent tax_code = row['TAX_CODE'] if not tax_code or tax_code == '/': tax_code = 'NA' if tax_code not in tax_codes: raise RuntimeError('undefined tax code %s' % tax_code) if tax_code == 'NA': if row['amount'] and not row['child_depend']: raise RuntimeError('TAX_CODE not specified ' 'for non-zero tax %s' % row['id']) if row['tax_code_id:id']: raise RuntimeError('tax_code_id specified ' 'for tax %s' % row['id']) else: if row['child_depend']: raise RuntimeError('TAX_CODE specified ' 'with child_depend for %s' % row['id']) if not row['amount']: raise RuntimeError('TAX_CODE specified ' 'for zero tax %s' % row['id']) if not row['tax_code_id:id']: raise RuntimeError('tax_code_id not specified ' 'for tax %s' % row['id']) if not row['child_depend'] and row['amount']: # ... in lux, we have the same code for invoice and refund assert row[ 'tax_code_id:id'], 'missing tax_code_id for %s' % row['id'] assert row['ref_tax_code_id:id'] == row['tax_code_id:id'] add_new_tax_code(row['tax_code_id:id'], 'Taxe - ' + row['name'], tax_code) for tax_code_id in sorted(new_tax_codes): name, parent_code = new_tax_codes[tax_code_id] writer.writerow([ tax_code_id, u'lu_tct_m' + parent_code, tax_code_id.replace('lu_tax_code_template_', u''), u'1', u'', pycompat.to_text(name), u'' ])
def _fetch_content(self): try: return super(JavascriptAsset, self)._fetch_content() except AssetError as e: return u"console.error(%s);" % json.dumps(to_text(e))
def load_information_from_description_file(module, mod_path=None): """ :param module: The name of the module (sale, purchase, ...) :param mod_path: Physical path of module, if not providedThe name of the module (sale, purchase, ...) """ if not mod_path: mod_path = get_module_path(module, downloaded=True) manifest_file = module_manifest(mod_path) if manifest_file: # default values for descriptor info = { 'application': False, 'author': 'Eagle ERP', 'auto_install': False, 'category': 'Uncategorized', 'depends': [], 'description': '', 'icon': get_module_icon(module), 'installable': True, 'license': 'LGPL-3', 'post_load': None, 'version': '1.0', 'web': False, 'sequence': 100, 'summary': '', 'website': '', } info.update( zip('depends data demo test init_xml update_xml demo_xml'.split(), iter(list, None))) f = tools.file_open(manifest_file, mode='rb') try: info.update(ast.literal_eval(pycompat.to_text(f.read()))) finally: f.close() if not info.get('description'): readme_path = [ opj(mod_path, x) for x in README if os.path.isfile(opj(mod_path, x)) ] if readme_path: with tools.file_open(readme_path[0]) as fd: info['description'] = fd.read() # auto_install is set to `False` if disabled, and a set of # auto_install dependencies otherwise. That way, we can set # auto_install: [] to always auto_install a module regardless of its # dependencies auto_install = info.get('auto_install', info.get('active', False)) if isinstance(auto_install, collections.Iterable): info['auto_install'] = set(auto_install) non_dependencies = info['auto_install'].difference(info['depends']) assert not non_dependencies,\ "auto_install triggers must be dependencies, found " \ "non-dependencies [%s] for module %s" % ( ', '.join(non_dependencies), module ) elif auto_install: info['auto_install'] = set(info['depends']) else: info['auto_install'] = False info['version'] = adapt_version(info['version']) return info _logger.debug('module %s: no manifest file found %s', module, MANIFEST_NAMES) return {}