diff --git a/openerp/addons/base/ir/ir_attachment.py b/openerp/addons/base/ir/ir_attachment.py index 730df212b4c..c80e1cf8a6e 100644 --- a/openerp/addons/base/ir/ir_attachment.py +++ b/openerp/addons/base/ir/ir_attachment.py @@ -64,10 +64,18 @@ class ir_attachment(osv.osv): return 0 return [] + # Work with a set, as list.remove() is prohibitive for large lists of documents + # (takes 20+ seconds on a db with 100k docs during search_count()!) + orig_ids = ids + ids = set(ids) + # For attachments, the permissions of the document they are attached to # apply, so we must remove attachments for which the user cannot access # the linked document. - targets = super(ir_attachment,self).read(cr, uid, ids, ['id', 'res_model', 'res_id']) + # Use pure SQL rather than read() as it is about 50% faster for large dbs (100k+ docs), + # and the permissions are checked in super() and below anyway. + cr.execute("""SELECT id, res_model, res_id FROM ir_attachment WHERE id = ANY(%s)""", (list(ids),)) + targets = cr.dictfetchall() model_attachments = {} for target_dict in targets: if not (target_dict['res_id'] and target_dict['res_model']): @@ -92,9 +100,10 @@ class ir_attachment(osv.osv): for res_id in disallowed_ids: for attach_id in targets[res_id]: ids.remove(attach_id) - if count: - return len(ids) - return ids + + # sort result according to the original sort ordering + result = [id for id in orig_ids if id in ids] + return len(result) if count else list(result) def read(self, cr, uid, ids, fields_to_read=None, context=None, load='_classic_read'): self.check(cr, uid, ids, 'read', context=context) diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py index 4bb279f3beb..bca8cbf4813 100644 --- a/openerp/addons/base/ir/ir_model.py +++ b/openerp/addons/base/ir/ir_model.py @@ -70,7 +70,10 @@ class ir_model(osv.osv): models = self.browse(cr, uid, ids, context=context) res = dict.fromkeys(ids) for model in models: - res[model.id] = self.pool.get(model.model).is_transient() + if self.pool.get(model.model): + res[model.id] = self.pool.get(model.model).is_transient() + else: + _logger.error('Missing model %s' % (model.model, )) return res def _search_osv_memory(self, cr, uid, model, name, domain, context=None): diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index 2b613778419..5c47584b498 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -153,7 +153,7 @@ class res_users(osv.osv): help="Specify a value only when creating a user or if you're "\ "changing the user's password, otherwise leave empty. After "\ "a change of password, the user has to login again."), - 'signature': fields.text('Signature', size=64), + 'signature': fields.text('Signature'), 'active': fields.boolean('Active'), 'action_id': fields.many2one('ir.actions.actions', 'Home Action', help="If specified, this action will be opened at logon for this user, in addition to the standard menu."), 'menu_id': fields.many2one('ir.actions.actions', 'Menu Action', help="If specified, the action will replace the standard menu for this user."), diff --git a/openerp/loglevels.py b/openerp/loglevels.py index 374efea8d84..f4fa46901dd 100644 --- a/openerp/loglevels.py +++ b/openerp/loglevels.py @@ -131,12 +131,13 @@ def ustr(value, hint_encoding='utf-8', errors='strict'): upstream and should be tried first to decode ``value``. :param str error: optional `errors` flag to pass to the unicode built-in to indicate how illegal character values should be - treated when converting a string: 'strict', 'ignore' or 'replace'. + treated when converting a string: 'strict', 'ignore' or 'replace' + (see ``unicode()`` constructor). Passing anything other than 'strict' means that the first encoding tried will be used, even if it's not the correct - one to use, so be careful! Ignore if value is not a string/unicode. - :rtype: unicode + one to use, so be careful! Ignored if value is not a string/unicode. :raise: UnicodeError if value cannot be coerced to unicode + :return: unicode string representing the given value """ if isinstance(value, Exception): return exception_to_unicode(value) diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index c8cf5909ecc..3e5c1198239 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -706,10 +706,7 @@ class many2many(_column): if where_c: where_c = ' AND ' + where_c - if offset or self._limit: - order_by = ' ORDER BY "%s".%s' %(obj._table, obj._order.split(',')[0]) - else: - order_by = '' + order_by = ' ORDER BY "%s".%s' %(obj._table, obj._order.split(',')[0]) limit_str = '' if self._limit is not None: diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 6a1f917caa1..3b9b8f30d23 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1111,7 +1111,7 @@ class BaseModel(object): if not model_data.search(cr, uid, [('name', '=', n)]): break postfix += 1 - model_data.create(cr, uid, { + model_data.create(cr, SUPERUSER_ID, { 'name': n, 'model': self._name, 'res_id': r['id'], @@ -1838,7 +1838,7 @@ class BaseModel(object): if trans: node.set('string', trans) - for attr_name in ('confirm', 'sum', 'help', 'placeholder'): + for attr_name in ('confirm', 'sum', 'avg', 'help', 'placeholder'): attr_value = node.get(attr_name) if attr_value: trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], attr_value) @@ -2670,13 +2670,19 @@ class BaseModel(object): order = orderby or groupby data_ids = self.search(cr, uid, [('id', 'in', alldata.keys())], order=order, context=context) - # the IDS of records that have groupby field value = False or '' should be sorted too - data_ids += filter(lambda x:x not in data_ids, alldata.keys()) - data = self.read(cr, uid, data_ids, groupby and [groupby] or ['id'], context=context) - # restore order of the search as read() uses the default _order (this is only for groups, so the size of data_read shoud be small): - data.sort(lambda x,y: cmp(data_ids.index(x['id']), data_ids.index(y['id']))) + + # the IDs of records that have groupby field value = False or '' should be included too + data_ids += set(alldata.keys()).difference(data_ids) + + if groupby: + data = self.read(cr, uid, data_ids, [groupby], context=context) + # restore order of the search as read() uses the default _order (this is only for groups, so the footprint of data should be small): + data_dict = dict((d['id'], d[groupby] ) for d in data) + result = [{'id': i, groupby: data_dict[i]} for i in data_ids] + else: + result = [{'id': i} for i in data_ids] - for d in data: + for d in result: if groupby: d['__domain'] = [(groupby, '=', alldata[d['id']][groupby] or False)] + domain if not isinstance(groupby_list, (str, unicode)): @@ -2697,11 +2703,11 @@ class BaseModel(object): del d['id'] if groupby and groupby in self._group_by_full: - data = self._read_group_fill_results(cr, uid, domain, groupby, groupby_list, - aggregated_fields, data, read_group_order=order, - context=context) + result = self._read_group_fill_results(cr, uid, domain, groupby, groupby_list, + aggregated_fields, result, read_group_order=order, + context=context) - return data + return result def _inherits_join_add(self, current_table, parent_model_name, query): """ @@ -3898,7 +3904,7 @@ class BaseModel(object): if isinstance(ids, (int, long)): ids = [ids] - result_store = self._store_get_values(cr, uid, ids, None, context) + result_store = self._store_get_values(cr, uid, ids, self._all_columns.keys(), context) self._check_concurrency(cr, ids, context) @@ -4305,11 +4311,16 @@ class BaseModel(object): del vals[self._inherits[table]] record_id = tocreate[table].pop('id', None) - + + # When linking/creating parent records, force context without 'no_store_function' key that + # defers stored functions computing, as these won't be computed in batch at the end of create(). + parent_context = dict(context) + parent_context.pop('no_store_function', None) + if record_id is None or not record_id: - record_id = self.pool.get(table).create(cr, user, tocreate[table], context=context) + record_id = self.pool.get(table).create(cr, user, tocreate[table], context=parent_context) else: - self.pool.get(table).write(cr, user, [record_id], tocreate[table], context=context) + self.pool.get(table).write(cr, user, [record_id], tocreate[table], context=parent_context) upd0 += ',' + self._inherits[table] upd1 += ',%s' @@ -5198,6 +5209,7 @@ class AbstractModel(BaseModel): """ _auto = False # don't create any database backend for AbstractModels _register = False # not visible in ORM registry, meant to be python-inherited only + _transient = False def itemgetter_tuple(items): """ Fixes itemgetter inconsistency (useful in some cases) of not returning diff --git a/openerp/report/interface.py b/openerp/report/interface.py index 17dc0973d7e..334d7d4056a 100644 --- a/openerp/report/interface.py +++ b/openerp/report/interface.py @@ -163,15 +163,30 @@ class report_rml(report_int): # * (re)build/update the stylesheet with the translated items def translate(doc, lang): - for node in doc.xpath('//*[@t]'): - if not node.text: - continue - translation = ir_translation_obj._get_source(cr, uid, self.name2, 'xsl', lang, node.text) - if translation: - node.text = translation + translate_aux(doc, lang, False) + + def translate_aux(doc, lang, t): + for node in doc: + t = t or node.get("t") + if t: + text = None + tail = None + if node.text: + text = node.text.strip().replace('\n',' ') + if node.tail: + tail = node.tail.strip().replace('\n',' ') + if text: + translation1 = ir_translation_obj._get_source(cr, uid, self.name2, 'xsl', lang, text) + if translation1: + node.text = node.text.replace(text, translation1) + if tail: + translation2 = ir_translation_obj._get_source(cr, uid, self.name2, 'xsl', lang, tail) + if translation2: + node.tail = node.tail.replace(tail, translation2) + translate_aux(node, lang, t) if context.get('lang', False): - translate(stylesheet, context['lang']) + translate(stylesheet.iter(), context['lang']) transform = etree.XSLT(stylesheet) xml = etree.tostring( diff --git a/openerp/report/render/rml2pdf/trml2pdf.py b/openerp/report/render/rml2pdf/trml2pdf.py index a17c03b40cb..b8dc9885a4a 100644 --- a/openerp/report/render/rml2pdf/trml2pdf.py +++ b/openerp/report/render/rml2pdf/trml2pdf.py @@ -99,7 +99,7 @@ class NumberedCanvas(canvas.Canvas): key = key + 1 self.setFont("Helvetica", 8) self.drawRightString((self._pagesize[0]-30), (self._pagesize[1]-40), - "Page %(this)i of %(total)i" % { + " %(this)i / %(total)i" % { 'this': self._pageNumber+1, 'total': self.pages.get(key,False), } @@ -118,15 +118,19 @@ class NumberedCanvas(canvas.Canvas): self._doc.SaveToFile(self._filename, self) class PageCount(platypus.Flowable): + def __init__(self, story_count=0): + platypus.Flowable.__init__(self) + self.story_count = story_count + def draw(self): - self.canv.beginForm("pageCount") + self.canv.beginForm("pageCount%d" % (self.story_count)) self.canv.setFont("Helvetica", utils.unit_get(str(8))) self.canv.drawString(0, 0, str(self.canv.getPageNumber())) self.canv.endForm() class PageReset(platypus.Flowable): def draw(self): - self.canv._pageNumber = 0 + self.canv._doPageReset = True class _rml_styles(object,): def __init__(self, nodes, localcontext): @@ -343,7 +347,7 @@ class _rml_canvas(object): if n.tag == 'pageCount': if x or y: self.canvas.translate(x,y) - self.canvas.doForm('pageCount') + self.canvas.doForm('pageCount%s' % (self.canvas._storyCount,)) if x or y: self.canvas.translate(-x,-y) if n.tag == 'pageNumber': @@ -878,6 +882,13 @@ class EndFrameFlowable(ActionFlowable): ActionFlowable.__init__(self,('frameEnd',resume)) class TinyDocTemplate(platypus.BaseDocTemplate): + + def beforeDocument(self): + # Store some useful value directly inside canvas, so it's available + # on flowable drawing (needed for proper PageCount handling) + self.canv._doPageReset = False + self.canv._storyCount = 0 + def ___handle_pageBegin(self): self.page = self.page + 1 self.pageTemplate.beforeDrawPage(self.canv,self) @@ -893,12 +904,24 @@ class TinyDocTemplate(platypus.BaseDocTemplate): self.frame = f break self.handle_frameBegin() - def afterFlowable(self, flowable): - if isinstance(flowable, PageReset): - self.canv._pageCount=self.page - self.page=0 - self.canv._flag=True + + def afterPage(self): + if self.canv._doPageReset: + # Following a tag: + # - we reset page number to 0 + # - we add an new PageCount flowable (relative to the current + # story number), but not for NumeredCanvas at is handle page + # count itself) + # NOTE: _rml_template render() method add a PageReset flowable at end + # of each story, so we're sure to pass here at least once per story. + if not isinstance(self.canv, NumberedCanvas): + self.handle_flowable([ PageCount(story_count=self.canv._storyCount) ]) + self.canv._pageCount = self.page + self.page = 0 + self.canv._flag = True self.canv._pageNumber = 0 + self.canv._doPageReset = False + self.canv._storyCount += 1 class _rml_template(object): def __init__(self, localcontext, out, node, doc, images=None, path='.', title=None): @@ -965,7 +988,6 @@ class _rml_template(object): self.doc_tmpl.afterFlowable(fis) self.doc_tmpl.build(fis,canvasmaker=NumberedCanvas) else: - fis.append(PageCount()) self.doc_tmpl.build(fis) def parseNode(rml, localcontext=None, fout=None, images=None, path='.', title=None): diff --git a/openerp/tools/config.py b/openerp/tools/config.py index 56f1e5c7b83..0b5a95d05f6 100644 --- a/openerp/tools/config.py +++ b/openerp/tools/config.py @@ -58,6 +58,8 @@ def check_ssl(): except: return False +DEFAULT_LOG_HANDLER = [':INFO'] + class configmanager(object): def __init__(self, fname=None): # Options not exposed on the command line. Command line options will be added @@ -179,7 +181,7 @@ class configmanager(object): group.add_option("--logfile", dest="logfile", help="file where the server log will be stored") group.add_option("--no-logrotate", dest="logrotate", action="store_false", my_default=True, help="do not rotate the logfile") group.add_option("--syslog", action="store_true", dest="syslog", my_default=False, help="Send the log to the syslog server") - group.add_option('--log-handler', action="append", default=[':INFO'], my_default=[':INFO'], metavar="PREFIX:LEVEL", help='setup a handler at LEVEL for a given PREFIX. An empty PREFIX indicates the root logger. This option can be repeated. Example: "openerp.orm:DEBUG" or "werkzeug:CRITICAL" (default: ":INFO")') + group.add_option('--log-handler', action="append", default=DEFAULT_LOG_HANDLER, my_default=DEFAULT_LOG_HANDLER, metavar="PREFIX:LEVEL", help='setup a handler at LEVEL for a given PREFIX. An empty PREFIX indicates the root logger. This option can be repeated. Example: "openerp.orm:DEBUG" or "werkzeug:CRITICAL" (default: ":INFO")') group.add_option('--log-request', action="append_const", dest="log_handler", const="openerp.netsvc.rpc.request:DEBUG", help='shortcut for --log-handler=openerp.netsvc.rpc.request:DEBUG') group.add_option('--log-response', action="append_const", dest="log_handler", const="openerp.netsvc.rpc.response:DEBUG", help='shortcut for --log-handler=openerp.netsvc.rpc.response:DEBUG') group.add_option('--log-web', action="append_const", dest="log_handler", const="openerp.addons.web.http:DEBUG", help='shortcut for --log-handler=openerp.addons.web.http:DEBUG') @@ -299,8 +301,9 @@ class configmanager(object): # Copy all optparse options (i.e. MyOption) into self.options. for group in parser.option_groups: for option in group.option_list: - self.options[option.dest] = option.my_default - self.casts[option.dest] = option + if option.dest not in self.options: + self.options[option.dest] = option.my_default + self.casts[option.dest] = option self.parse_config(None, False) @@ -384,13 +387,18 @@ class configmanager(object): ] for arg in keys: - # Copy the command-line argument... - if getattr(opt, arg): + # Copy the command-line argument (except the special case for log_handler, due to + # action=append requiring a real default, so we cannot use the my_default workaround) + if getattr(opt, arg) and getattr(opt, arg) != DEFAULT_LOG_HANDLER: self.options[arg] = getattr(opt, arg) # ... or keep, but cast, the config file value. elif isinstance(self.options[arg], basestring) and self.casts[arg].type in optparse.Option.TYPE_CHECKER: self.options[arg] = optparse.Option.TYPE_CHECKER[self.casts[arg].type](self.casts[arg], arg, self.options[arg]) + + if isinstance(self.options['log_handler'], basestring): + self.options['log_handler'] = self.options['log_handler'].split(',') + # if defined but None take the configfile value keys = [ 'language', 'translate_out', 'translate_in', 'overwrite_existing_translations', @@ -613,6 +621,9 @@ class configmanager(object): def __setitem__(self, key, value): self.options[key] = value + if key in self.options and isinstance(self.options[key], basestring) and \ + key in self.casts and self.casts[key].type in optparse.Option.TYPE_CHECKER: + self.options[key] = optparse.Option.TYPE_CHECKER[self.casts[key].type](self.casts[key], key, self.options[key]) def __getitem__(self, key): return self.options[key] diff --git a/openerp/tools/mail.py b/openerp/tools/mail.py index 0711aa982aa..a54f99ba1ad 100644 --- a/openerp/tools/mail.py +++ b/openerp/tools/mail.py @@ -358,10 +358,11 @@ def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=Non """ # If not cr, get cr from current thread database + local_cr = None if not cr: db_name = getattr(threading.currentThread(), 'dbname', None) if db_name: - cr = pooler.get_db_only(db_name).cursor() + local_cr = cr = pooler.get_db(db_name).cursor() else: raise Exception("No database cursor found, please pass one explicitly") @@ -380,7 +381,8 @@ def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=Non _logger.exception("tools.email_send failed to deliver email") return False finally: - cr.close() + if local_cr: + cr.close() return res def email_split(text): diff --git a/openerp/tools/translate.py b/openerp/tools/translate.py index 2284e9a8965..439cd508122 100644 --- a/openerp/tools/translate.py +++ b/openerp/tools/translate.py @@ -167,19 +167,22 @@ class GettextAlias(object): if db_name: return sql_db.db_connect(db_name) - def _get_cr(self, frame): + def _get_cr(self, frame, allow_create=True): is_new_cr = False cr = frame.f_locals.get('cr', frame.f_locals.get('cursor')) if not cr: s = frame.f_locals.get('self', {}) cr = getattr(s, 'cr', None) - if not cr: + if not cr and allow_create: db = self._get_db() if db: cr = db.cursor() is_new_cr = True return cr, is_new_cr + def _get_uid(self, frame): + return frame.f_locals.get('uid') or frame.f_locals.get('user') + def _get_lang(self, frame): lang = None ctx = frame.f_locals.get('context') @@ -194,11 +197,21 @@ class GettextAlias(object): ctx = kwargs.get('context') if ctx: lang = ctx.get('lang') + s = frame.f_locals.get('self', {}) if not lang: - s = frame.f_locals.get('self', {}) c = getattr(s, 'localcontext', None) if c: lang = c.get('lang') + if not lang: + # Last resort: attempt to guess the language of the user + # Pitfall: some operations are performed in sudo mode, and we + # don't know the originial uid, so the language may + # be wrong when the admin language differs. + pool = getattr(s, 'pool', None) + (cr, dummy) = self._get_cr(frame, allow_create=False) + uid = self._get_uid(frame) + if pool and cr and uid: + lang = pool.get('res.users').context_get(cr, uid)['lang'] return lang def __call__(self, source): @@ -443,12 +456,17 @@ def trans_export(lang, modules, buffer, format, cr): for module, type, name, res_id, src, trad, comments in rows: row = grouped_rows.setdefault(src, {}) row.setdefault('modules', set()).add(module) - if ('translation' not in row) or (not row['translation']): + if not row.get('translation') and trad != src: row['translation'] = trad row.setdefault('tnrs', []).append((type, name, res_id)) row.setdefault('comments', set()).update(comments) for src, row in grouped_rows.items(): + if not lang: + # translation template, so no translation value + row['translation'] = '' + elif not row.get('translation'): + row['translation'] = src writer.write(row['modules'], row['tnrs'], src, row['translation'], row['comments']) elif format == 'tgz': @@ -484,16 +502,25 @@ def trans_export(lang, modules, buffer, format, cr): del translations def trans_parse_xsl(de): + return list(set(trans_parse_xsl_aux(de, False))) + +def trans_parse_xsl_aux(de, t): res = [] + for n in de: - if n.get("t"): - for m in n: - if isinstance(m, SKIPPED_ELEMENT_TYPES) or not m.text: + t = t or n.get("t") + if t: + if isinstance(n, SKIPPED_ELEMENT_TYPES) or n.tag.startswith('{http://www.w3.org/1999/XSL/Transform}'): continue - l = m.text.strip().replace('\n',' ') - if len(l): - res.append(l.encode("utf8")) - res.extend(trans_parse_xsl(n)) + if n.text: + l = n.text.strip().replace('\n',' ') + if len(l): + res.append(l.encode("utf8")) + if n.tail: + l = n.tail.strip().replace('\n',' ') + if len(l): + res.append(l.encode("utf8")) + res.extend(trans_parse_xsl_aux(n, t)) return res def trans_parse_rml(de): diff --git a/openerp/tools/yaml_import.py b/openerp/tools/yaml_import.py index ca7ab92a225..26752a8aa82 100644 --- a/openerp/tools/yaml_import.py +++ b/openerp/tools/yaml_import.py @@ -350,35 +350,37 @@ class YamlInterpreter(object): return one2many_view def process_val(key, val): - if fg[key]['type']=='many2one': + if fg[key]['type'] == 'many2one': if type(val) in (tuple,list): val = val[0] - elif (fg[key]['type']=='one2many'): - if val is False: - val = [] - if len(val) and type(val[0]) == dict: - #we want to return only the fields that aren't readonly - #For that, we need to first get the right tree view to consider for the field `key´ + elif fg[key]['type'] == 'one2many': + if val and isinstance(val, (list,tuple)) and isinstance(val[0], dict): + # we want to return only the fields that aren't readonly + # For that, we need to first get the right tree view to consider for the field `key´ one2many_tree_view = _get_right_one2many_view(fg, key, 'tree') + arch = etree.fromstring(one2many_tree_view['arch'].encode('utf-8')) for rec in val: - #make a copy for the iteration, as we will alterate the size of `rec´ dictionary + # make a copy for the iteration, as we will alter `rec´ rec_copy = rec.copy() for field_key in rec_copy: - #seek in the view for the field `field_key´ and removing it from `key´ values, as this column is readonly in the tree view - subfield_obj = etree.fromstring(one2many_tree_view['arch'].encode('utf-8')).xpath("//field[@name='%s']"%(field_key)) - if subfield_obj and (subfield_obj[0].get('modifiers', '{}').find('"readonly": true') >= 0): - #TODO: currently we only support if readonly is True in the modifiers. Some improvement may be done in - #order to support also modifiers that look like {"readonly": [["state", "not in", ["draft", "confirm"]]]} - del(rec[field_key]) - - #now that unwanted values have been removed from val, we can encapsulate it in a tuple as returned value + # if field is missing in view or has a readonly modifier, drop it + field_elem = arch.xpath("//field[@name='%s']" % field_key) + if field_elem and (field_elem[0].get('modifiers', '{}').find('"readonly": true') >= 0): + # TODO: currently we only support if readonly is True in the modifiers. Some improvement may be done in + # order to support also modifiers that look like {"readonly": [["state", "not in", ["draft", "confirm"]]]} + del rec[field_key] + # now that unwanted values have been removed from val, we can encapsulate it in a tuple as returned value val = map(lambda x: (0,0,x), val) + elif fg[key]['type'] == 'many2many': + if val and isinstance(val,(list,tuple)) and isinstance(val[0], (int,long)): + val = [(6,0,val)] - #we want to return only the fields that aren't readonly + # we want to return only the fields that aren't readonly if el.get('modifiers', '{}').find('"readonly": true') >= 0: - #TODO: currently we only support if readonly is True in the modifiers. Some improvement may be done in - #order to support also modifiers that look like {"readonly": [["state", "not in", ["draft", "confirm"]]]} + # TODO: currently we only support if readonly is True in the modifiers. Some improvement may be done in + # order to support also modifiers that look like {"readonly": [["state", "not in", ["draft", "confirm"]]]} return False + return val if view_info: diff --git a/win32/setup.py b/win32/setup.py index 9c064c1f6d8..2dfb8ef3493 100644 --- a/win32/setup.py +++ b/win32/setup.py @@ -19,9 +19,16 @@ # ############################################################################## +import os +import glob from distutils.core import setup import py2exe +def datas(): + r = [] + if os.name == 'nt': + r.append(("Microsoft.VC90.CRT", glob.glob('C:\Microsoft.VC90.CRT\*.*'))) + return r setup(service=["OpenERPServerService"], options={"py2exe":{"excludes":["Tkconstants","Tkinter","tcl", @@ -29,7 +36,8 @@ setup(service=["OpenERPServerService"], "ImageTk", "PIL.ImageTk", "FixTk"], "skip_archive": 1, - "optimize": 2,}} + "optimize": 2,}}, + data_files=datas(), ) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: