diff --git a/bin/addons/__init__.py b/bin/addons/__init__.py index 025b2a7b772..395cf573c0a 100644 --- a/bin/addons/__init__.py +++ b/bin/addons/__init__.py @@ -45,11 +45,17 @@ from cStringIO import StringIO logger = netsvc.Logger() _ad = os.path.abspath(opj(tools.config['root_path'], 'addons')) # default addons path (base) -ad = os.path.abspath(tools.config['addons_path']) # alternate addons path +ad_paths= map(lambda m: os.path.abspath(m.strip()),tools.config['addons_path'].split(',')) sys.path.insert(1, _ad) -if ad != _ad: - sys.path.insert(1, ad) + +ad_cnt=1 +for adp in ad_paths: + if adp != _ad: + sys.path.insert(ad_cnt, adp) + ad_cnt+=1 + +ad_paths.append(_ad) # for get_module_path # Modules already loaded loaded = [] @@ -153,11 +159,10 @@ class Node(Singleton): def get_module_path(module, downloaded=False): """Return the path of the given module.""" - if os.path.exists(opj(ad, module)) or os.path.exists(opj(ad, '%s.zip' % module)): - return opj(ad, module) + for adp in ad_paths: + if os.path.exists(opj(adp, module)) or os.path.exists(opj(adp, '%s.zip' % module)): + return opj(adp, module) - if os.path.exists(opj(_ad, module)) or os.path.exists(opj(_ad, '%s.zip' % module)): - return opj(_ad, module) if downloaded: return opj(_ad, module) logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: module not found' % (module,)) @@ -282,7 +287,10 @@ def get_modules(): return os.path.isdir(name) or zipfile.is_zipfile(name) return map(clean, filter(is_really_module, os.listdir(dir))) - return list(set(listdir(ad) + listdir(_ad))) + plist = [] + for ad in ad_paths: + plist.extend(listdir(ad)) + return list(set(plist)) def get_modules_with_version(): modules = get_modules() @@ -310,6 +318,7 @@ def upgrade_graph(graph, cr, module_list, force=None): mod_path = get_module_path(module) terp_file = get_module_resource(module, '__terp__.py') if not mod_path or not terp_file: + logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: not installable' % (module)) cr.execute("update ir_module_module set state=%s where name=%s", ('uninstallable', module)) continue @@ -325,6 +334,7 @@ def upgrade_graph(graph, cr, module_list, force=None): dependencies = dict([(p, deps) for p, deps, data in packages]) current, later = set([p for p, dep, data in packages]), set() + while packages and current > later: package, deps, data = packages[0] @@ -394,7 +404,7 @@ def register_class(m): try: zip_mod_path = mod_path + '.zip' if not os.path.isfile(zip_mod_path): - fm = imp.find_module(m, [ad, _ad]) + fm = imp.find_module(m, ad_paths) try: imp.load_module(m, *fm) finally: @@ -571,6 +581,8 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, **kwargs): has_updates = False modobj = None + logger.notifyChannel('init', netsvc.LOG_DEBUG, 'loading %d packages..' % len(graph)) + for package in graph: logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading objects' % package.name) migrations.migrate_module(package, 'pre') @@ -751,7 +763,11 @@ def load_modules(db, force_demo=False, status=None, update_module=False): cr.execute('select model,res_id from ir_model_data where noupdate=%s and module=%s order by id desc', (False, mod_name,)) for rmod, rid in cr.fetchall(): uid = 1 - pool.get(rmod).unlink(cr, uid, [rid]) + rmod_module= pool.get(rmod) + if rmod_module: + rmod_module.unlink(cr, uid, [rid]) + else: + logger.notifyChannel('init', netsvc.LOG_ERROR, 'Could not locate %s to remove res=%d' % (rmod,rid)) cr.execute('delete from ir_model_data where noupdate=%s and module=%s', (False, mod_name,)) cr.commit() # diff --git a/bin/addons/base/base_data.xml b/bin/addons/base/base_data.xml index 64c5f191fbb..710bddeb73d 100644 --- a/bin/addons/base/base_data.xml +++ b/bin/addons/base/base_data.xml @@ -574,7 +574,7 @@ mh - Macedonia + FYROM mk diff --git a/bin/addons/base/ir/ir.xml b/bin/addons/base/ir/ir.xml index c6c96a91aae..1658be358d5 100644 --- a/bin/addons/base/ir/ir.xml +++ b/bin/addons/base/ir/ir.xml @@ -115,6 +115,8 @@ + + diff --git a/bin/addons/base/ir/ir_actions.py b/bin/addons/base/ir/ir_actions.py index 3524a4857a6..8bfc0740781 100644 --- a/bin/addons/base/ir/ir_actions.py +++ b/bin/addons/base/ir/ir_actions.py @@ -127,6 +127,7 @@ class report_xml(osv.osv): ('html', 'html'), ('raw', 'raw'), ('sxw', 'sxw'), + ('txt', 'txt'), ('odt', 'odt'), ('html2html','HTML from HTML'), ('mako2html','HTML from HTML(Mako)'), diff --git a/bin/addons/base/ir/ir_attachment.py b/bin/addons/base/ir/ir_attachment.py index 554110b3880..89d4bbd49dd 100644 --- a/bin/addons/base/ir/ir_attachment.py +++ b/bin/addons/base/ir/ir_attachment.py @@ -30,7 +30,7 @@ class ir_attachment(osv.osv): ima = self.pool.get('ir.model.access') if isinstance(ids, (int, long)): ids = [ids] - cr.execute('select distinct res_model from ir_attachment where id in ('+','.join(map(str, ids))+')') + cr.execute('select distinct res_model from ir_attachment where id = ANY (%s)', (ids,)) for obj in cr.fetchall(): if obj[0]: ima.check(cr, uid, obj[0], mode, context=context) diff --git a/bin/addons/base/ir/ir_cron.py b/bin/addons/base/ir/ir_cron.py index e4cae8569c7..18889d46a2a 100644 --- a/bin/addons/base/ir/ir_cron.py +++ b/bin/addons/base/ir/ir_cron.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- ############################################################################## # -# OpenERP, Open Source Management Solution +# OpenERP, Open Source Management Solution # Copyright (C) 2004-2009 Tiny SPRL (). All Rights Reserved # $Id$ # @@ -25,7 +25,7 @@ import time import netsvc import tools import pooler -from osv import fields,osv +from osv import fields, osv def str2tuple(s): return eval('tuple(%s)' % s) @@ -76,7 +76,7 @@ class ir_cron(osv.osv, netsvc.Agent): return False return True - _constraints= [ + _constraints = [ (_check_args, 'Invalid arguments', ['args']), ] @@ -92,7 +92,7 @@ class ir_cron(osv.osv, netsvc.Agent): self._logger.notifyChannel('timers', netsvc.LOG_ERROR, tools.exception_to_unicode(e)) - def _poolJobs(self, db_name, check=False): + def _poolJobs(self, db_name, check=False): try: db, pool = pooler.get_db_and_pool(db_name) except: @@ -115,7 +115,7 @@ class ir_cron(osv.osv, netsvc.Agent): if numbercall: nextcall += _intervalTypes[job['interval_type']](job['interval_number']) ok = True - addsql='' + addsql = '' if not numbercall: addsql = ', active=False' cr.execute("update ir_cron set nextcall=%s, numbercall=%s"+addsql+" where id=%s", (nextcall.strftime('%Y-%m-%d %H:%M:%S'), numbercall, job['id'])) @@ -124,13 +124,18 @@ class ir_cron(osv.osv, netsvc.Agent): cr.execute('select min(nextcall) as min_next_call from ir_cron where numbercall<>0 and active and nextcall>=now()') next_call = cr.dictfetchone()['min_next_call'] - if next_call: + if next_call: next_call = time.mktime(time.strptime(next_call, '%Y-%m-%d %H:%M:%S')) else: next_call = int(time.time()) + 3600 # if do not find active cron job from database, it will run again after 1 day if not check: self.setAlarm(self._poolJobs, next_call, db_name, db_name) + + except Exception, ex: + logger = netsvc.Logger() + logger.notifyChannel('cron', netsvc.LOG_WARNING, + 'Exception in cron:'+str(ex)) finally: cr.commit() diff --git a/bin/addons/base/ir/ir_sequence.py b/bin/addons/base/ir/ir_sequence.py index f1334b6996a..2f8f75ccf5e 100644 --- a/bin/addons/base/ir/ir_sequence.py +++ b/bin/addons/base/ir/ir_sequence.py @@ -21,6 +21,7 @@ import time from osv import fields,osv +from tools.safe_eval import safe_eval import pooler class ir_sequence_type(osv.osv): @@ -46,6 +47,8 @@ class ir_sequence(osv.osv): 'number_next': fields.integer('Next Number', required=True), 'number_increment': fields.integer('Increment Number', required=True), 'padding' : fields.integer('Number padding', required=True), + 'condition': fields.char('Condition', size=250, help="If set, sequence will only be used in case this python expression matches, and will precede other sequences."), + 'weight': fields.integer('Weight',required=True, help="If two sequences match, the highest weight will be used."), 'company_id' : fields.many2one('res.company', 'Company'), } _defaults = { @@ -53,6 +56,7 @@ class ir_sequence(osv.osv): 'number_increment': lambda *a: 1, 'number_next': lambda *a: 1, 'padding' : lambda *a : 0, + 'weight' : lambda *a: 10, 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'ir.sequence', c) } @@ -72,10 +76,29 @@ class ir_sequence(osv.osv): } def get_id(self, cr, uid, sequence_id, test='id=%s', context=None): + if not context: + context = {} try: - cr.execute('SELECT id, number_next, prefix, suffix, padding FROM ir_sequence WHERE '+test+' AND active=%s FOR UPDATE', (sequence_id, True)) - res = cr.dictfetchone() - if res: + cr.execute('SELECT id, number_next, prefix, suffix, padding, condition \ + FROM ir_sequence \ + WHERE '+test+' AND active=%s ORDER BY weight DESC, length(COALESCE(condition,\'\')) DESC \ + FOR UPDATE', (sequence_id, True)) + for res in cr.dictfetchall(): + if res['condition']: + print "ir_seq: %s has condition:" %res['id'], res['condition'], + try: + bo = safe_eval(res['condition'],context) + if not bo: + print "not matched" + continue + except Exception,e: + # it would be normal to have exceptions, because + # the domain may contain errors + print "Exception.\ne:",e + print "Context:", context + continue + print "Matched!" + cr.execute('UPDATE ir_sequence SET number_next=number_next+number_increment WHERE id=%s AND active=%s', (res['id'], True)) if res['number_next']: return self._process(res['prefix']) + '%%0%sd' % res['padding'] % res['number_next'] + self._process(res['suffix']) @@ -85,8 +108,8 @@ class ir_sequence(osv.osv): cr.commit() return False - def get(self, cr, uid, code): - return self.get_id(cr, uid, code, test='code=%s') + def get(self, cr, uid, code, context = None): + return self.get_id(cr, uid, code, test='code=%s',context=context) ir_sequence() diff --git a/bin/addons/base/ir/ir_values.py b/bin/addons/base/ir/ir_values.py index 64196007fd2..355a1ac01d1 100644 --- a/bin/addons/base/ir/ir_values.py +++ b/bin/addons/base/ir/ir_values.py @@ -249,8 +249,7 @@ class ir_values(osv.osv): if r[2].has_key('groups_id'): groups = r[2]['groups_id'] if len(groups) > 0: - group_ids = ','.join([ str(x) for x in r[2]['groups_id']]) - cr.execute("select count(*) from res_groups_users_rel where gid in (%s) and uid='%s'" % (group_ids, uid)) + cr.execute("SELECT count(*) FROM res_groups_users_rel WHERE gid = ANY(%s) AND uid=%s",(groups, uid)) gr_ids = cr.fetchall() if not gr_ids[0][0] > 0: res2.remove(r) diff --git a/bin/addons/base/ir/workflow/print_instance.py b/bin/addons/base/ir/workflow/print_instance.py index e70be5bebbe..bac8eedb476 100644 --- a/bin/addons/base/ir/workflow/print_instance.py +++ b/bin/addons/base/ir/workflow/print_instance.py @@ -81,7 +81,10 @@ def graph_get(cr, graph, wkf_id, nested=False, workitem={}): start = cr.fetchone()[0] cr.execute("select 'subflow.'||name,id from wkf_activity where flow_stop=True and wkf_id=%s", (wkf_id,)) stop = cr.fetchall() - stop = (stop[0][1], dict(stop)) + if (stop): + stop = (stop[0][1], dict(stop)) + else: + stop = ("stop",{}) return ((start,{}),stop) @@ -143,7 +146,7 @@ showpage''' else: inst_id = inst_id[0] graph = pydot.Dot(fontsize='16', label="""\\\n\\nWorkflow: %s\\n OSV: %s""" % (wkfinfo['name'],wkfinfo['osv']), - size='10.7, 7.3', center='1', ratio='auto', rotate='90', rankdir='LR' + size='7.3, 10.1', center='1', ratio='auto', rotate='0', rankdir='TB', ) graph_instance_get(cr, graph, inst_id, data.get('nested', False)) ps_string = graph.create(prog='dot', format='ps') diff --git a/bin/addons/base/module/report/ir_module_reference.rml b/bin/addons/base/module/report/ir_module_reference.rml index 1e09766e3e6..10117c7130a 100644 --- a/bin/addons/base/module/report/ir_module_reference.rml +++ b/bin/addons/base/module/report/ir_module_reference.rml @@ -233,6 +233,12 @@ Object: [[ object.model ]] [[ objdoc(object.model) ]] + + + [[ repeatIn(objdoc2(object.model) or [], 'sline') ]] + [[ sline ]] + +
diff --git a/bin/addons/base/module/report/ir_module_reference_print.py b/bin/addons/base/module/report/ir_module_reference_print.py index ff1fee02955..7364499af7e 100644 --- a/bin/addons/base/module/report/ir_module_reference_print.py +++ b/bin/addons/base/module/report/ir_module_reference_print.py @@ -29,11 +29,38 @@ class ir_module_reference_print(report_sxw.rml_parse): 'time': time, 'findobj': self._object_find, 'objdoc': self._object_doc, + 'objdoc2': self._object_doc2, 'findflds': self._fields_find, }) def _object_doc(self, obj): modobj = self.pool.get(obj) - return modobj.__doc__ + strdocs= modobj.__doc__ + if not strdocs: + return None + else: + strdocs=strdocs.strip().splitlines(True) + res = '' + for stre in strdocs: + if not stre or stre.isspace(): + break + res += stre + return res + + def _object_doc2(self, obj): + modobj = self.pool.get(obj) + strdocs= modobj.__doc__ + if not strdocs: + return None + else: + strdocs=strdocs.strip().splitlines(True) + res = [] + fou = False + for stre in strdocs: + if fou: + res.append(stre.strip()) + elif not stre or stre.isspace(): + fou = True + return res def _object_find(self, module): ids2 = self.pool.get('ir.model.data').search(self.cr, self.uid, [('module','=',module), ('model','=','ir.model')]) diff --git a/bin/addons/base/module/wizard/wizard_export_lang.py b/bin/addons/base/module/wizard/wizard_export_lang.py index 41ebbca64cb..1e538b95cfb 100644 --- a/bin/addons/base/module/wizard/wizard_export_lang.py +++ b/bin/addons/base/module/wizard/wizard_export_lang.py @@ -79,7 +79,8 @@ class wizard_export_lang(osv.osv_memory): ('get','get'), # get the file ) ), } - _defaults = { 'state': lambda *a: 'choose', + _defaults = { 'state': lambda *a: 'choose', + 'name': lambda *a: 'lang.tar.gz' } wizard_export_lang() diff --git a/bin/addons/base/res/country_view.xml b/bin/addons/base/res/country_view.xml index a1852bffd62..81459ff8860 100644 --- a/bin/addons/base/res/country_view.xml +++ b/bin/addons/base/res/country_view.xml @@ -71,7 +71,7 @@ - States + Fed. States ir.actions.act_window res.country.state form diff --git a/bin/addons/base/res/ir_property.py b/bin/addons/base/res/ir_property.py index 952c32930cf..e14e365dce3 100644 --- a/bin/addons/base/res/ir_property.py +++ b/bin/addons/base/res/ir_property.py @@ -58,7 +58,7 @@ class ir_property(osv.osv): } def unlink(self, cr, uid, ids, context={}): if ids: - cr.execute('delete from ir_model_fields where id in (select fields_id from ir_property where (fields_id is not null) and (id in ('+','.join(map(str, ids))+')))') + cr.execute('DELETE FROM ir_model_fields WHERE id IN (SELECT fields_id FROM ir_property WHERE (fields_id IS NOT NULL) AND (id = ANY (%s)))', (ids,)) res = super(ir_property, self).unlink(cr, uid, ids, context) return res diff --git a/bin/addons/base/res/res_company.py b/bin/addons/base/res/res_company.py index 3d481cdfa6d..9cfa2c4670a 100644 --- a/bin/addons/base/res/res_company.py +++ b/bin/addons/base/res/res_company.py @@ -113,11 +113,11 @@ class res_company(osv.osv): - + [[ formatLang(time.strftime("%Y-%m-%d"), date=True) ]] [[ time.strftime("%H:%M") ]] - + [[ company.partner_id.name ]] - + / @@ -136,7 +136,7 @@ class res_company(osv.osv): [[company.logo]] - + 1.3cm 27.7cm 20cm 27.7cm diff --git a/bin/addons/base/res/res_user.py b/bin/addons/base/res/res_user.py index 5b610096012..d6b7cd6eec0 100644 --- a/bin/addons/base/res/res_user.py +++ b/bin/addons/base/res/res_user.py @@ -71,6 +71,14 @@ class groups(osv.osv): aid.write({'groups_id': [(4, gid)]}) return gid + def copy(self, cr, uid, id, default={}, context={}, done_list=[], local=False): + group = self.browse(cr, uid, id, context=context) + default = default.copy() + if not 'name' in default: + default['name'] = group['name'] + default['name'] = default['name'] + _(' (copy)') + return super(groups, self).copy(cr, uid, id, default, context=context) + groups() diff --git a/bin/addons/base/rng/view.rng b/bin/addons/base/rng/view.rng index 37f681de92f..e5db7ddf7f9 100644 --- a/bin/addons/base/rng/view.rng +++ b/bin/addons/base/rng/view.rng @@ -12,6 +12,7 @@ + @@ -45,6 +46,7 @@ + @@ -187,6 +189,7 @@ + @@ -226,6 +229,7 @@ + @@ -243,6 +247,7 @@ + @@ -366,6 +371,7 @@ + @@ -489,6 +495,15 @@ + + + + + + + + + diff --git a/bin/import_xml.rng b/bin/import_xml.rng index 1ba1ff3831f..1e1167656b0 100644 --- a/bin/import_xml.rng +++ b/bin/import_xml.rng @@ -96,6 +96,7 @@ + @@ -118,6 +119,7 @@ + diff --git a/bin/netsvc.py b/bin/netsvc.py index 57d8e7831d2..8abdf4d7901 100644 --- a/bin/netsvc.py +++ b/bin/netsvc.py @@ -36,17 +36,23 @@ import time import xmlrpclib import release -SERVICES = {} -GROUPS = {} - class Service(object): + """ Base class for *Local* services + + Functionality here is trusted, no authentication. + """ + _services = {} def __init__(self, name, audience=''): - SERVICES[name] = self + Service._services[name] = self self.__name = name self._methods = {} def joinGroup(self, name): - GROUPS.setdefault(name, {})[self.__name] = self + raise Exception("No group for local services") + #GROUPS.setdefault(name, {})[self.__name] = self + + def service_exist(self,name): + return Service._services.has_key(name) def exportMethod(self, method): if callable(method): @@ -58,11 +64,16 @@ class Service(object): else: raise -class LocalService(Service): +class LocalService(object): + """ Proxy for local services. + + Any instance of this class will behave like the single instance + of Service(name) + """ def __init__(self, name): self.__name = name try: - self._service = SERVICES[name] + self._service = Service._services[name] for method_name, method_definition in self._service._methods.items(): setattr(self, method_name, method_definition) except KeyError, keyError: @@ -71,19 +82,55 @@ class LocalService(Service): def __call__(self, method, *params): return getattr(self, method)(*params) -def service_exist(name): - return SERVICES.get(name, False) +class ExportService(object): + """ Proxy for exported services. + + All methods here should take an AuthProxy as their first parameter. It + will be appended by the calling framework. + + Note that this class has no direct proxy, capable of calling + eservice.method(). Rather, the proxy should call + dispatch(method,auth,params) + """ + + _services = {} + _groups = {} + + def __init__(self, name, audience=''): + ExportService._services[name] = self + self.__name = name + + def joinGroup(self, name): + ExportService._groups.setdefault(name, {})[self.__name] = self + + @classmethod + def getService(cls,name): + return cls._services[name] + + def dispatch(self, method, auth, params): + raise Exception("stub dispatch at %s" % self.__name) + + def new_dispatch(self,method,auth,params): + raise Exception("stub dispatch at %s" % self.__name) + + def abortResponse(self, error, description, origin, details): + if not tools.config['debug_mode']: + raise Exception("%s -- %s\n\n%s"%(origin, description, details)) + else: + raise LOG_NOTSET = 'notset' LOG_DEBUG_RPC = 'debug_rpc' LOG_DEBUG = 'debug' +LOG_DEBUG2 = 'debug2' LOG_INFO = 'info' LOG_WARNING = 'warn' LOG_ERROR = 'error' LOG_CRITICAL = 'critical' # add new log level below DEBUG -logging.DEBUG_RPC = logging.DEBUG - 1 +logging.DEBUG2 = logging.DEBUG - 1 +logging.DEBUG_RPC = logging.DEBUG2 - 1 def init_logger(): import os @@ -94,7 +141,6 @@ def init_logger(): # create a format for log messages and dates formatter = logging.Formatter('[%(asctime)s] %(levelname)s:%(name)s:%(message)s') - logging_to_stdout = False if tools.config['syslog']: # SysLog Handler if os.name == 'nt': @@ -112,15 +158,18 @@ def init_logger(): dirname = os.path.dirname(logf) if dirname and not os.path.isdir(dirname): os.makedirs(dirname) - handler = logging.handlers.TimedRotatingFileHandler(logf,'D',1,30) + if tools.config['logrotate'] is not False: + handler = logging.handlers.TimedRotatingFileHandler(logf,'D',1,30) + elif os.name == 'posix': + handler = logging.handlers.WatchedFileHandler(logf) + else: + handler = logging.handlers.FileHandler(logf) except Exception, ex: sys.stderr.write("ERROR: couldn't create the logfile directory. Logging to the standard output.\n") handler = logging.StreamHandler(sys.stdout) - logging_to_stdout = True else: # Normal Handler on standard output handler = logging.StreamHandler(sys.stdout) - logging_to_stdout = True # tell the handler to use this format @@ -128,9 +177,9 @@ def init_logger(): # add the handler to the root logger logger.addHandler(handler) - logger.setLevel(tools.config['log_level'] or '0') + logger.setLevel(int(tools.config['log_level'] or '0')) - if logging_to_stdout and os.name != 'nt': + if (not isinstance(handler, logging.FileHandler)) and os.name != 'nt': # change color of level names # uses of ANSI color codes # see http://pueblo.sourceforge.net/doc/manual/ansi_color_codes.html @@ -141,6 +190,7 @@ def init_logger(): mapping = { 'DEBUG_RPC': ('blue', 'white'), + 'DEBUG2': ('green', 'white'), 'DEBUG': ('blue', 'default'), 'INFO': ('green', 'default'), 'WARNING': ('yellow', 'default'), @@ -160,6 +210,10 @@ class Logger(object): log = logging.getLogger(tools.ustr(name)) + if level == LOG_DEBUG2 and not hasattr(log, level): + fct = lambda msg, *args, **kwargs: log.log(logging.DEBUG2, msg, *args, **kwargs) + setattr(log, LOG_DEBUG2, fct) + if level == LOG_DEBUG_RPC and not hasattr(log, level): fct = lambda msg, *args, **kwargs: log.log(logging.DEBUG_RPC, msg, *args, **kwargs) setattr(log, LOG_DEBUG_RPC, fct) @@ -169,17 +223,33 @@ class Logger(object): if isinstance(msg, Exception): msg = tools.exception_to_unicode(msg) - msg = tools.ustr(msg).strip() - - if level in (LOG_ERROR,LOG_CRITICAL): - msg = common().get_server_environment() + '\n' + msg + try: + msg = tools.ustr(msg).strip() + if level in (LOG_ERROR,LOG_CRITICAL) and tools.config.get_misc('debug','env_info',True): + msg = common().exp_get_server_environment() + "\n" + msg - result = msg.split('\n') - if len(result)>1: - for idx, s in enumerate(result): - level_method('[%02d]: %s' % (idx+1, s,)) - elif result: - level_method(result[0]) + result = msg.split('\n') + except UnicodeDecodeError: + result = msg.strip().split('\n') + try: + if len(result)>1: + for idx, s in enumerate(result): + level_method('[%02d]: %s' % (idx+1, s,)) + elif result: + level_method(result[0]) + except IOError,e: + # TODO: perhaps reset the logger streams? + #if logrotate closes our files, we end up here.. + pass + except: + # better ignore the exception and carry on.. + pass + + def set_loglevel(self, level): + log = logging.getLogger() + log.setLevel(logging.INFO) # make sure next msg is printed + log.info("Log level changed to %s" % logging.getLevelName(level)) + log.setLevel(level) def shutdown(self): logging.shutdown() @@ -194,7 +264,7 @@ class Agent(object): def setAlarm(self, fn, dt, db_name, *args, **kwargs): wait = dt - time.time() if wait > 0: - self._logger.notifyChannel('timers', LOG_DEBUG, "Job scheduled in %s seconds for %s.%s" % (wait, fn.im_class.__name__, fn.func_name)) + self._logger.notifyChannel('timers', LOG_DEBUG, "Job scheduled in %.3g seconds for %s.%s" % (wait, fn.im_class.__name__, fn.func_name)) timer = threading.Timer(wait, fn, args, kwargs) timer.start() self._timers.setdefault(db_name, []).append(timer) @@ -218,10 +288,66 @@ class Agent(object): import traceback -class xmlrpc(object): - class RpcGateway(object): - def __init__(self, name): - self.name = name +class Server: + """ Generic interface for all servers with an event loop etc. + Override this to impement http, net-rpc etc. servers. + + Servers here must have threaded behaviour. start() must not block, + there is no run(). + """ + __is_started = False + __servers = [] + + def __init__(self): + if Server.__is_started: + raise Exception('All instances of servers must be inited before the startAll()') + Server.__servers.append(self) + + def start(self): + print "called stub Server.start" + pass + + def stop(self): + print "called stub Server.stop" + pass + + def stats(self): + """ This function should return statistics about the server """ + return "%s: No statistics" % str(self.__class__) + + @classmethod + def startAll(cls): + if cls.__is_started: + return + Logger().notifyChannel("services", LOG_INFO, + "Starting %d services" % len(cls.__servers)) + for srv in cls.__servers: + srv.start() + cls.__is_started = True + + @classmethod + def quitAll(cls): + if not cls.__is_started: + return + Logger().notifyChannel("services", LOG_INFO, + "Stopping %d services" % len(cls.__servers)) + for srv in cls.__servers: + srv.stop() + cls.__is_started = False + + @classmethod + def allStats(cls): + res = '' + if cls.__is_started: + res += "Servers started\n" + else: + res += "Servers stopped\n" + for srv in cls.__servers: + try: + res += srv.stats() + "\n" + except: + pass + return res class OpenERPDispatcherException(Exception): def __init__(self, exception, traceback): @@ -234,14 +360,19 @@ class OpenERPDispatcher: Logger().notifyChannel('%s' % title, LOG_DEBUG_RPC, pformat(msg)) def dispatch(self, service_name, method, params): - if service_name not in GROUPS['web-services']: - raise Exception('Access Denied for Service :'+service_name) try: self.log('service', service_name) self.log('method', method) self.log('params', params) - result = LocalService(service_name)(method, *params) + if hasattr(self,'auth_provider'): + auth = self.auth_provider + else: + auth = None + result = ExportService.getService(service_name).dispatch(method, auth, params) self.log('result', result) + # We shouldn't marshall None, + if result == None: + result = False return result except Exception, e: self.log('exception', tools.exception_to_unicode(e)) @@ -255,174 +386,4 @@ class OpenERPDispatcher: pdb.post_mortem(tb[2]) raise OpenERPDispatcherException(e, tb_s) -class GenericXMLRPCRequestHandler(OpenERPDispatcher): - def _dispatch(self, method, params): - try: - service_name = self.path.split("/")[-1] - return self.dispatch(service_name, method, params) - except OpenERPDispatcherException, e: - raise xmlrpclib.Fault(tools.exception_to_unicode(e.exception), e.traceback) - -class SSLSocket(object): - def __init__(self, socket): - if not hasattr(socket, 'sock_shutdown'): - from OpenSSL import SSL - ctx = SSL.Context(SSL.SSLv23_METHOD) - ctx.use_privatekey_file(tools.config['secure_pkey_file']) - ctx.use_certificate_file(tools.config['secure_cert_file']) - self.socket = SSL.Connection(ctx, socket) - else: - self.socket = socket - - def shutdown(self, how): - return self.socket.sock_shutdown(how) - - def __getattr__(self, name): - return getattr(self.socket, name) - -class SimpleXMLRPCRequestHandler(GenericXMLRPCRequestHandler, SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): - rpc_paths = map(lambda s: '/xmlrpc/%s' % s, GROUPS.get('web-services', {}).keys()) - -class SecureXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): - def setup(self): - self.connection = SSLSocket(self.request) - self.rfile = socket._fileobject(self.request, "rb", self.rbufsize) - self.wfile = socket._fileobject(self.request, "wb", self.wbufsize) - -class SimpleThreadedXMLRPCServer(SocketServer.ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer): - def server_bind(self): - self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - SimpleXMLRPCServer.SimpleXMLRPCServer.server_bind(self) - -class SecureThreadedXMLRPCServer(SimpleThreadedXMLRPCServer): - def __init__(self, server_address, HandlerClass, logRequests=1): - SimpleThreadedXMLRPCServer.__init__(self, server_address, HandlerClass, logRequests) - self.socket = SSLSocket(socket.socket(self.address_family, self.socket_type)) - self.server_bind() - self.server_activate() - -class HttpDaemon(threading.Thread): - def __init__(self, interface, port, secure=False): - threading.Thread.__init__(self) - self.__port = port - self.__interface = interface - self.secure = bool(secure) - handler_class = (SimpleXMLRPCRequestHandler, SecureXMLRPCRequestHandler)[self.secure] - server_class = (SimpleThreadedXMLRPCServer, SecureThreadedXMLRPCServer)[self.secure] - - if self.secure: - from OpenSSL.SSL import Error as SSLError - else: - class SSLError(Exception): pass - try: - self.server = server_class((interface, port), handler_class, 0) - except SSLError, e: - Logger().notifyChannel('xml-rpc-ssl', LOG_CRITICAL, "Can not load the certificate and/or the private key files") - sys.exit(1) - except Exception, e: - Logger().notifyChannel('xml-rpc', LOG_CRITICAL, "Error occur when starting the server daemon: %s" % (e,)) - sys.exit(1) - - - def attach(self, path, gw): - pass - - def stop(self): - self.running = False - if os.name != 'nt': - self.server.socket.shutdown( hasattr(socket, 'SHUT_RDWR') and socket.SHUT_RDWR or 2 ) - self.server.socket.close() - - def run(self): - self.server.register_introspection_functions() - - self.running = True - while self.running: - self.server.handle_request() - return True - - # If the server need to be run recursively - # - #signal.signal(signal.SIGALRM, self.my_handler) - #signal.alarm(6) - #while True: - # self.server.handle_request() - #signal.alarm(0) # Disable the alarm - -import tiny_socket -class TinySocketClientThread(threading.Thread, OpenERPDispatcher): - def __init__(self, sock, threads): - threading.Thread.__init__(self) - self.sock = sock - self.threads = threads - - def run(self): - import select - self.running = True - try: - ts = tiny_socket.mysocket(self.sock) - except: - self.sock.close() - self.threads.remove(self) - return False - while self.running: - try: - msg = ts.myreceive() - except: - self.sock.close() - self.threads.remove(self) - return False - try: - result = self.dispatch(msg[0], msg[1], msg[2:]) - ts.mysend(result) - except OpenERPDispatcherException, e: - new_e = Exception(tools.exception_to_unicode(e.exception)) # avoid problems of pickeling - ts.mysend(new_e, exception=True, traceback=e.traceback) - - self.sock.close() - self.threads.remove(self) - return True - - def stop(self): - self.running = False - - -class TinySocketServerThread(threading.Thread): - def __init__(self, interface, port, secure=False): - threading.Thread.__init__(self) - self.__port = port - self.__interface = interface - self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self.socket.bind((self.__interface, self.__port)) - self.socket.listen(5) - self.threads = [] - - def run(self): - import select - try: - self.running = True - while self.running: - (clientsocket, address) = self.socket.accept() - ct = TinySocketClientThread(clientsocket, self.threads) - self.threads.append(ct) - ct.start() - self.socket.close() - except Exception, e: - self.socket.close() - return False - - def stop(self): - self.running = False - for t in self.threads: - t.stop() - try: - if hasattr(socket, 'SHUT_RDWR'): - self.socket.shutdown(socket.SHUT_RDWR) - else: - self.socket.shutdown(2) - self.socket.close() - except: - return False - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/bin/openerp-server.py b/bin/openerp-server.py index 3d07c9de047..dbf1caa7532 100755 --- a/bin/openerp-server.py +++ b/bin/openerp-server.py @@ -36,6 +36,7 @@ GNU Public Licence. import sys import os import signal +import pwd #---------------------------------------------------------- # ubuntu 8.04 has obsoleted `pyxml` package and installs here. # the path needs to be updated before any `import xml` @@ -48,11 +49,16 @@ if os.path.exists(_oldxml1): elif os.path.exists(_oldxml2): sys.path.insert(0,_oldxml2) - import release __author__ = release.author __version__ = release.version +# We DON't log this using the standard logger, because we might mess +# with the logfile's permissions. Just do a quick exit here. +if pwd.getpwuid(os.getuid())[0] == 'root' : + sys.stderr.write("Attempted to run OpenERP server as root. This is not good, aborting.\n") + sys.exit(1) + #---------------------------------------------------------- # get logger #---------------------------------------------------------- @@ -105,6 +111,17 @@ import addons # Load and update databases if requested #---------------------------------------------------------- +import service.http_server + +if not ( tools.config["stop_after_init"] or \ + tools.config["translate_in"] or \ + tools.config["translate_out"] ): + service.http_server.init_servers() + service.http_server.init_xmlrpc() + + import service.netrpc_server + service.netrpc_server.init_servers() + if tools.config['db_name']: for db in tools.config['db_name'].split(','): pooler.get_db_and_pool(db, update_module=tools.config['init'] or tools.config['update']) @@ -145,36 +162,9 @@ if tools.config["stop_after_init"]: #---------------------------------------------------------- -# Launch Server +# Launch Servers #---------------------------------------------------------- -if tools.config['xmlrpc']: - port = int(tools.config['port']) - interface = tools.config["interface"] - secure = tools.config["secure"] - - httpd = netsvc.HttpDaemon(interface, port, secure) - - xml_gw = netsvc.xmlrpc.RpcGateway('web-services') - httpd.attach("/xmlrpc", xml_gw) - logger.notifyChannel("web-services", netsvc.LOG_INFO, - "starting XML-RPC%s services, port %s" % - ((tools.config['secure'] and ' Secure' or ''), port)) - -# -#if tools.config["soap"]: -# soap_gw = netsvc.xmlrpc.RpcGateway('web-services') -# httpd.attach("/soap", soap_gw ) -# logger.notifyChannel("web-services", netsvc.LOG_INFO, 'starting SOAP services, port '+str(port)) -# - -if tools.config['netrpc']: - netport = int(tools.config['netport']) - netinterface = tools.config["netinterface"] - tinySocket = netsvc.TinySocketServerThread(netinterface, netport, False) - logger.notifyChannel("web-services", netsvc.LOG_INFO, - "starting NET-RPC service, port %d" % (netport,)) - LST_SIGNALS = ['SIGINT', 'SIGTERM'] if os.name == 'posix': LST_SIGNALS.extend(['SIGUSR1','SIGQUIT']) @@ -189,11 +179,8 @@ def handler(signum, _): :param signum: the signal number :param _: """ - if tools.config['netrpc']: - tinySocket.stop() - if tools.config['xmlrpc']: - httpd.stop() netsvc.Agent.quit() + netsvc.Server.quitAll() if tools.config['pidfile']: os.unlink(tools.config['pidfile']) logger.notifyChannel('shutdown', netsvc.LOG_INFO, @@ -210,16 +197,14 @@ if tools.config['pidfile']: fd.write(pidtext) fd.close() + +netsvc.Server.startAll() + logger.notifyChannel("web-services", netsvc.LOG_INFO, 'the server is running, waiting for connections...') -if tools.config['netrpc']: - tinySocket.start() -if tools.config['xmlrpc']: - httpd.start() - while True: - time.sleep(1) + time.sleep(60) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/bin/osv/expression.py b/bin/osv/expression.py index 0a98b7c2118..31a02e8f244 100644 --- a/bin/osv/expression.py +++ b/bin/osv/expression.py @@ -36,7 +36,7 @@ class expression(object): return isinstance(element, (str, unicode)) and element in ['&', '|', '!'] def _is_leaf(self, element, internal=False): - OPS = ('=', '!=', '<>', '<=', '<', '>', '>=', '=like', '=ilike', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of') + OPS = ('=', '!=', '<>', '<=', '<', '>', '>=', '=?', '=like', '=ilike', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of') INTERNAL_OPS = OPS + ('inselect',) return (isinstance(element, tuple) or isinstance(element, list)) \ and len(element) == 3 \ @@ -44,6 +44,7 @@ class expression(object): or (internal and element[1] in INTERNAL_OPS)) def __execute_recursive_in(self, cr, s, f, w, ids, op, type): + # todo: merge into parent query as sub-query res = [] if ids: if op in ['<','>','>=','<=']: @@ -56,9 +57,8 @@ class expression(object): subids = ids[i:i+cr.IN_MAX] cr.execute('SELECT "%s"' \ ' FROM "%s"' \ - ' WHERE "%s" in (%s)' % (s, f, w, ','.join(['%s']*len(subids))), - subids) - res.extend([r[0] for r in cr.fetchall()]) + ' WHERE "%s" in (%s)' % (s, f, w, ','.join(['%s']*len(subids))), + subids) else: cr.execute('SELECT distinct("%s")' \ ' FROM "%s" where "%s" is not null' % (s, f, s)), @@ -236,6 +236,8 @@ class expression(object): if operator == 'child_of': if isinstance(right, basestring): ids2 = [x[0] for x in field_obj.name_search(cr, uid, right, [], 'like', limit=None)] + elif isinstance(right, (int, long)): + ids2 = list([right]) else: ids2 = list(right) @@ -343,6 +345,18 @@ class expression(object): query = '(%s.%s IS NOT NULL and %s.%s != false)' % (table._table, left,table._table, left) elif (((right == False) and (type(right)==bool)) or right is None) and (operator in ['<>', '!=']): query = '%s.%s IS NOT NULL' % (table._table, left) + elif (operator == '=?'): + op = '=' + if (right is False or right is None): + return ( 'TRUE',[]) + if left in table._columns: + format = table._columns[left]._symbol_set[0] + query = '(%s.%s %s %s)' % (table._table, left, op, format) + params = table._columns[left]._symbol_set[1](right) + else: + query = "(%s.%s %s '%%s')" % (table._table, left, op) + params = right + else: if left == 'id': query = '%s.id %s %%s' % (table._table, operator) diff --git a/bin/osv/fields.py b/bin/osv/fields.py index 074dcad5c5e..dde80f0ffb9 100644 --- a/bin/osv/fields.py +++ b/bin/osv/fields.py @@ -459,7 +459,7 @@ class one2many(_column): elif act[0] == 6: obj.write(cr, user, act[2], {self._fields_id:id}, context=context or {}) ids2 = act[2] or [0] - cr.execute('select id from '+_table+' where '+self._fields_id+'=%s and id not in ('+','.join(map(str, ids2))+')', (id,)) + cr.execute('select id from '+_table+' where '+self._fields_id+'=%s and id <> ALL (%s)', (id,ids2)) ids3 = map(lambda x:x[0], cr.fetchall()) obj.write(cr, user, ids3, {self._fields_id:False}, context=context or {}) return result @@ -504,7 +504,6 @@ class many2many(_column): return res for id in ids: res[id] = [] - ids_s = ','.join(map(str, ids)) limit_str = self._limit is not None and ' limit %d' % self._limit or '' obj = obj.pool.get(self._obj) @@ -514,10 +513,10 @@ class many2many(_column): cr.execute('SELECT '+self._rel+'.'+self._id2+','+self._rel+'.'+self._id1+' \ FROM '+self._rel+' , '+obj._table+' \ - WHERE '+self._rel+'.'+self._id1+' in ('+ids_s+') \ + WHERE '+self._rel+'.'+self._id1+' = ANY (%s) \ AND '+self._rel+'.'+self._id2+' = '+obj._table+'.id '+d1 +limit_str+' order by '+obj._table+'.'+obj._order+' offset %s', - d2+[offset]) + [ids,]+d2+[offset]) for r in cr.fetchall(): res[r[1]].append(r[0]) return res @@ -585,6 +584,16 @@ class many2many(_column): obj.datas[id][name] = act[2] +def get_nice_size(a): + (x,y) = a + if isinstance(y, (int,long)): + size = y + elif y: + y = len(y) + else: + y = 0 + return (x, tools.human_size(size)) + # --------------------------------------------------------- # Function fields # --------------------------------------------------------- @@ -665,7 +674,7 @@ class function(_column): if self._type == 'binary' and context.get('bin_size', False): # convert the data returned by the function with the size of that data... - res = dict(map(lambda (x, y): (x, tools.human_size(len(y or ''))), res.items())) + res = dict(map( get_nice_size, res.items())) return res get_memory = get diff --git a/bin/osv/orm.py b/bin/osv/orm.py index 04d493ce95e..c7ab469bcd4 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -94,10 +94,10 @@ class browse_null(object): self.id = False def __getitem__(self, name): - return False + return None def __getattr__(self, name): - return False # XXX: return self ? + return None # XXX: return self ? def __int__(self): return False @@ -165,7 +165,7 @@ class browse_record(object): col = self._table._columns[name] elif name in self._table._inherit_fields: col = self._table._inherit_fields[name][2] - elif hasattr(self._table, name): + elif hasattr(self._table, str(name)): if isinstance(getattr(self._table, name), (types.MethodType, types.LambdaType, types.FunctionType)): return lambda *args, **argv: getattr(self._table, name)(self._cr, self._uid, [self._id], *args, **argv) else: @@ -173,7 +173,7 @@ class browse_record(object): else: logger = netsvc.Logger() logger.notifyChannel('orm', netsvc.LOG_ERROR, "Programming error: field '%s' does not exist in object '%s' !" % (name, self._table._name)) - return False + return None # if the field is a classic one or a many2one, we'll fetch all classic and many2one fields if col._prefetch: @@ -205,6 +205,9 @@ class browse_record(object): d[n].set_value(self._cr, self._uid, d[n], self, f, lang_obj) + if not datas: + # Where did those ids come from? Perhaps old entries in ir_model_data? + raise except_orm('NoDataError', 'Field %s in %s%s'%(name,self._table_name,str(ids))) # create browse records for 'remote' objects for data in datas: for n, f in ffields: @@ -225,6 +228,12 @@ class browse_record(object): elif f._type in ('one2many', 'many2many') and len(data[n]): data[n] = self._list_class([browse_record(self._cr, self._uid, id, self._table.pool.get(f._obj), self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process) for id in data[n]], self._context) self._data[data['id']].update(data) + if not name in self._data[self._id]: + #how did this happen? + logger = netsvc.Logger() + logger.notifyChannel("browse_record", netsvc.LOG_ERROR,"Ffields: %s, datas: %s"%(str(fffields),str(datas))) + logger.notifyChannel("browse_record", netsvc.LOG_ERROR,"Data: %s, Table: %s"%(str(self._data[self._id]),str(self._table))) + raise AttributeError(_('Unknown attribute %s in %s ') % (str(name),self._table_name)) return self._data[self._id][name] def __getattr__(self, name): @@ -662,8 +671,10 @@ class orm_template(object): else: module, xml_id = current_module, line[i] id = ir_model_data_obj._get_id(cr, uid, module, xml_id) - res_id = ir_model_data_obj.read(cr, uid, [id], - ['res_id'])[0]['res_id'] + res_res_id = ir_model_data_obj.read(cr, uid, [id], + ['res_id']) + if res_res_id: + res_id = res_res_id[0]['res_id'] row[field[-1][:-3]] = res_id or False continue if (len(field) == len(prefix)+1) and \ @@ -951,6 +962,7 @@ class orm_template(object): and getattr(self._columns[f], arg): res[f][arg] = getattr(self._columns[f], arg) + #TODO: optimize res_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'field', context.get('lang', False) or 'en_US') if res_trans: res[f]['string'] = res_trans @@ -1344,8 +1356,8 @@ class orm_template(object): # otherwise, build some kind of default view if view_type == 'form': res = self.fields_get(cr, user, context=context) - xml = '''''' \ - '''
''' % (self._description,) + xml = ' ' \ + '' % (self._description,) for x in res: if res[x]['type'] not in ('one2many', 'many2many'): xml += '' % (x,) @@ -1356,18 +1368,19 @@ class orm_template(object): _rec_name = self._rec_name if _rec_name not in self._columns: _rec_name = self._columns.keys()[0] - xml = '''''' \ - '''''' \ - % (self._description, self._rec_name) + xml = '' \ + '' \ + % (self._description, self._rec_name) elif view_type == 'calendar': xml = self.__get_default_calendar_view() else: - raise except_orm(_('Invalid Architecture!'),_("There is no view of type '%s' defined for the structure!") % view_type) + xml = '' # what happens here, graph case? + # raise except_orm(_('Invalid Architecture!'),_("There is no view of type '%s' defined for the structure!") % view_type) result['arch'] = etree.fromstring(encode(xml)) result['name'] = 'default' result['field_parent'] = False result['view_id'] = 0 - + xarch, xfields = self.__view_look_dom_arch(cr, user, result['arch'], view_id, context=context) result['arch'] = xarch result['fields'] = xfields @@ -1448,6 +1461,7 @@ class orm_template(object): self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'read', context=context) if not fields: fields = self._columns.keys() + self._inherit_fields.keys() + #FIXME: collect all calls to _get_source into one SQL call. for lang in langs: res[lang] = {'code': lang} for f in fields: @@ -1469,6 +1483,7 @@ class orm_template(object): def write_string(self, cr, uid, id, langs, vals, context=None): self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'write', context=context) + #FIXME: try to only call the translation in one SQL for lang in langs: for field in vals: if field in self._columns: @@ -1717,7 +1732,7 @@ class orm_memory(orm_template): if id in self.datas: del self.datas[id] if len(ids): - cr.execute('delete from wkf_instance where res_type=%s and res_id in ('+','.join(map(str, ids))+')', (self._name, )) + cr.execute('delete from wkf_instance where res_type=%s and res_id = ANY (%s)', (self._name,ids)) return True def perm_read(self, cr, user, ids, context=None, details=True): @@ -1893,6 +1908,20 @@ class orm(orm_template): "AND c.oid=a.attrelid " \ "AND a.atttypid=t.oid", (self._table, k)) res = cr.dictfetchall() + if not res and hasattr(f,'oldname'): + cr.execute("SELECT c.relname,a.attname,a.attlen,a.atttypmod,a.attnotnull,a.atthasdef,t.typname,CASE WHEN a.attlen=-1 THEN a.atttypmod-4 ELSE a.attlen END as size " \ + "FROM pg_class c,pg_attribute a,pg_type t " \ + "WHERE c.relname=%s " \ + "AND a.attname=%s " \ + "AND c.oid=a.attrelid " \ + "AND a.atttypid=t.oid", (self._table, f.oldname)) + res_old = cr.dictfetchall() + logger.notifyChannel('orm', netsvc.LOG_DEBUG, 'trying to rename %s(%s) to %s'% (self._table, f.oldname, k)) + if res_old and len(res_old)==1: + cr.execute('ALTER TABLE "%s" RENAME "%s" TO "%s"' % ( self._table,f.oldname, k)) + res = res_old + res[0]['attname'] = k + if not res: if not isinstance(f, fields.function) or f.store: @@ -1944,7 +1973,7 @@ class orm(orm_template): f_pg_type = f_pg_def['typname'] f_pg_size = f_pg_def['size'] f_pg_notnull = f_pg_def['attnotnull'] - if isinstance(f, fields.function) and not f.store: + if isinstance(f, fields.function) and not f.store and (not hasattr(f,'nodrop') or not f.nodrop): logger.notifyChannel('orm', netsvc.LOG_INFO, 'column %s (%s) in table %s removed: converted to a function !\n' % (k, f.string, self._table)) cr.execute('ALTER TABLE "%s" DROP COLUMN "%s"'% (self._table, k)) cr.commit() @@ -2052,7 +2081,7 @@ class orm(orm_template): cr.execute('ALTER TABLE "' + self._table + '" ADD FOREIGN KEY ("' + k + '") REFERENCES "' + ref + '" ON DELETE ' + f.ondelete) cr.commit() else: - logger.notifyChannel('orm', netsvc.LOG_ERROR, "Programming error !") + logger.notifyChannel('orm', netsvc.LOG_ERROR, "Programming error, column %s->%s has multiple instances !"%(self._table,k)) for order,f,k in todo_update_store: todo_end.append((order, self._update_store, (f, k))) @@ -2266,9 +2295,11 @@ class orm(orm_template): self.pool.get('ir.model.access').check(cr, user, self._name, 'read', context=context) if not fields: fields = self._columns.keys() + self._inherit_fields.keys() - select = ids if isinstance(ids, (int, long)): select = [ids] + else: + select = map(int,ids) + select = map(lambda x: isinstance(x,dict) and x['id'] or x, select) result = self._read_flat(cr, user, select, fields, context, load) @@ -2315,18 +2346,16 @@ class orm(orm_template): for i in range(0, len(ids), cr.IN_MAX): sub_ids = ids[i:i+cr.IN_MAX] if d1: - cr.execute('SELECT %s FROM \"%s\" WHERE id IN (%s) AND %s ORDER BY %s' % \ - (','.join(fields_pre2 + ['id']), self._table, - ','.join(['%s' for x in sub_ids]), d1, - self._order),sub_ids + d2) + cr.execute('SELECT %s FROM \"%s\" WHERE id = ANY (%%s) AND %s ORDER BY %s' % \ + (','.join(fields_pre2 + ['id']), self._table, d1, + self._order),[sub_ids,]+d2) if not cr.rowcount == len({}.fromkeys(sub_ids)): raise except_orm(_('AccessError'), _('You try to bypass an access rule (Document type: %s).') % self._description) else: - cr.execute('SELECT %s FROM \"%s\" WHERE id IN (%s) ORDER BY %s' % \ + cr.execute('SELECT %s FROM \"%s\" WHERE id = ANY (%%s) ORDER BY %s' % \ (','.join(fields_pre2 + ['id']), self._table, - ','.join(['%s' for x in sub_ids]), - self._order), sub_ids) + self._order), (sub_ids,)) res.extend(cr.dictfetchall()) else: res = map(lambda x: {'id': x}, ids) @@ -2336,6 +2365,7 @@ class orm(orm_template): continue if self._columns[f].translate: ids = map(lambda x: x['id'], res) + #TODO: optimize out of this loop res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids) for r in res: r[f] = res_trans.get(r['id'], False) or r[f] @@ -2651,6 +2681,7 @@ class orm(orm_template): 'where id in ('+ids_str+')', upd1) if totranslate: + # TODO: optimize for f in direct: if self._columns[f].translate: src_trans = self.pool.get(self._name).read(cr,user,ids,[f]) @@ -3172,18 +3203,20 @@ class orm(orm_template): elif ftype in ('one2many', 'one2one'): res = [] rel = self.pool.get(fields[f]['relation']) - for rel_id in data[f]: - # the lines are first duplicated using the wrong (old) - # parent but then are reassigned to the correct one thanks - # to the (4, ...) - d,t = rel.copy_data(cr, uid, rel_id, context=context) - res.append((0, 0, d)) - trans_data += t + if data[f] != False: + for rel_id in data[f]: + # the lines are first duplicated using the wrong (old) + # parent but then are reassigned to the correct one thanks + # to the (4, ...) + d,t = rel.copy_data(cr, uid, rel_id, context=context) + res.append((0, 0, d)) + trans_data += t data[f] = res elif ftype == 'many2many': data[f] = [(6, 0, data[f])] trans_obj = self.pool.get('ir.translation') + #TODO: optimize translations trans_name='' for f in fields: trans_flag=True @@ -3232,7 +3265,7 @@ class orm(orm_template): sub_ids_parent = ids_parent[i:i+cr.IN_MAX] cr.execute('SELECT distinct "'+parent+'"'+ ' FROM "'+self._table+'" ' \ - 'WHERE id in ('+','.join(map(str, sub_ids_parent))+')') + 'WHERE id = ANY(%s)',(sub_ids_parent,)) ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall()))) ids_parent = ids_parent2 for i in ids_parent: diff --git a/bin/report/interface.py b/bin/report/interface.py index 8c44bc38de9..d50107f507b 100644 --- a/bin/report/interface.py +++ b/bin/report/interface.py @@ -48,7 +48,7 @@ def toxml(val): class report_int(netsvc.Service): def __init__(self, name, audience='*'): - assert not netsvc.service_exist(name), 'The report "%s" already exist!' % name + assert not self.service_exist(name), 'The report "%s" already exist!' % name super(report_int, self).__init__(name, audience) if name[0:7]<>'report.': raise Exception, 'ConceptionError, bad report name, should start with "report."' @@ -56,7 +56,7 @@ class report_int(netsvc.Service): self.id = 0 self.name2 = '.'.join(name.split('.')[1:]) self.title = None - self.joinGroup('report') + #self.joinGroup('report') self.exportMethod(self.create) def create(self, cr, uid, ids, datas, context=None): @@ -80,6 +80,7 @@ class report_rml(report_int): 'html': self.create_html, 'raw': self.create_raw, 'sxw': self.create_sxw, + 'txt': self.create_txt, 'odt': self.create_odt, 'html2html' : self.create_html2html, 'makohtml2html' :self.create_makohtml2html, @@ -204,11 +205,17 @@ class report_rml(report_int): obj.render() return obj.get() + def create_txt(self, rml,localcontext, logo=None, title=None): + obj = render.rml2txt(rml, localcontext, self.bin_datas) + obj.render() + return obj.get().encode('utf-8') + def create_html2html(self, rml, localcontext = None, logo=None, title=None): obj = render.html2html(rml, localcontext, self.bin_datas) obj.render() return obj.get() + def create_raw(self,rml, localcontext = None, logo=None, title=None): obj = render.odt2odt(etree.XML(rml),localcontext) obj.render() @@ -237,8 +244,9 @@ def register_all(db): cr.execute("SELECT * FROM ir_act_report_xml WHERE auto=%s ORDER BY id", (True,)) result = cr.dictfetchall() cr.close() + svcs = netsvc.Service._services for r in result: - if netsvc.service_exist('report.'+r['report_name']): + if svcs.has_key('report.'+r['report_name']): continue if r['report_rml'] or r['report_rml_content_data']: report_sxw('report.'+r['report_name'], r['model'], @@ -250,4 +258,3 @@ def register_all(db): # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - diff --git a/bin/report/render/__init__.py b/bin/report/render/__init__.py index d28626dad86..1a888e18b75 100644 --- a/bin/report/render/__init__.py +++ b/bin/report/render/__init__.py @@ -20,7 +20,7 @@ ############################################################################## from simple import simple -from rml import rml, rml2html, odt2odt, html2html, makohtml2html +from rml import rml, rml2html, rml2txt, odt2odt , html2html, makohtml2html from render import render try: diff --git a/bin/report/render/rml.py b/bin/report/render/rml.py index 5b6b9dc9c8d..c30549b4bb0 100644 --- a/bin/report/render/rml.py +++ b/bin/report/render/rml.py @@ -22,6 +22,7 @@ import render import rml2pdf import rml2html as htmlizer +import rml2txt as txtizer import odt2odt as odt import html2html as html import makohtml2html as makohtml @@ -50,6 +51,16 @@ class rml2html(render.render): def _render(self): return htmlizer.parseString(self.rml,self.localcontext) +class rml2txt(render.render): + def __init__(self, rml, localcontext= None, datas={}): + super(rml2txt, self).__init__(datas) + self.rml = rml + self.localcontext = localcontext + self.output_type = 'txt' + + def _render(self): + return txtizer.parseString(self.rml, self.localcontext) + class odt2odt(render.render): def __init__(self, rml, localcontext = None, datas = {}): render.render.__init__(self, datas) diff --git a/bin/report/render/rml2html/rml2html.py b/bin/report/render/rml2html/rml2html.py index b70c87b7f01..c197dae7675 100644 --- a/bin/report/render/rml2html/rml2html.py +++ b/bin/report/render/rml2html/rml2html.py @@ -454,7 +454,7 @@ if __name__=="__main__": rml2html_help() print parseString(file(sys.argv[1], 'r').read()), else: - print 'Usage: trml2pdf input.rml >output.pdf' - print 'Try \'trml2pdf --help\' for more information.' + print 'Usage: rml2html input.rml >output.html' + print 'Try \'rml2html --help\' for more information.' # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file diff --git a/bin/report/render/rml2pdf/customfonts.py b/bin/report/render/rml2pdf/customfonts.py new file mode 100644 index 00000000000..3418c63f226 --- /dev/null +++ b/bin/report/render/rml2pdf/customfonts.py @@ -0,0 +1,66 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 P. Christeas, Tiny SPRL (). +# All Rights Reserved +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## + +from reportlab import rl_config +import os + +CustomTTFonts = [ ('Helvetica',"DejaVu Sans", "DejaVuSans.ttf", 'normal'), + ('Helvetica',"DejaVu Sans Bold", "DejaVuSans-Bold.ttf", 'bold'), + ('Helvetica',"DejaVu Sans Oblique", "DejaVuSans-Oblique.ttf", 'italic'), + ('Helvetica',"DejaVu Sans BoldOblique", "DejaVuSans-BoldOblique.ttf", 'bolditalic'), + ('Times',"Liberation Serif", "LiberationSerif-Regular.ttf", 'normal'), + ('Times',"Liberation Serif Bold", "LiberationSerif-Bold.ttf", 'bold'), + ('Times',"Liberation Serif Italic", "LiberationSerif-Italic.ttf", 'italic'), + ('Times',"Liberation Serif BoldItalic", "LiberationSerif-BoldItalic.ttf", 'bolditalic'), + ('Times-Roman',"Liberation Serif", "LiberationSerif-Regular.ttf", 'normal'), + ('Times-Roman',"Liberation Serif Bold", "LiberationSerif-Bold.ttf", 'bold'), + ('Times-Roman',"Liberation Serif Italic", "LiberationSerif-Italic.ttf", 'italic'), + ('Times-Roman',"Liberation Serif BoldItalic", "LiberationSerif-BoldItalic.ttf", 'bolditalic'), + ('ZapfDingbats',"DejaVu Serif", "DejaVuSerif.ttf", 'normal'), + ('ZapfDingbats',"DejaVu Serif Bold", "DejaVuSerif-Bold.ttf", 'bold'), + ('ZapfDingbats',"DejaVu Serif Italic", "DejaVuSerif-Italic.ttf", 'italic'), + ('ZapfDingbats',"DejaVu Serif BoldItalic", "DejaVuSerif-BoldItalic.ttf", 'bolditalic'), + ('Courier',"FreeMono", "FreeMono.ttf", 'normal'), + ('Courier',"FreeMono Bold", "FreeMonoBold.ttf", 'bold'), + ('Courier',"FreeMono Oblique", "FreeMonoOblique.ttf", 'italic'), + ('Courier',"FreeMono BoldOblique", "FreeMonoBoldOblique.ttf", 'bolditalic'),] + +def SearchFontPath(font_file): + for dirname in rl_config.TTFSearchPath: + for root, dirs, files in os.walk(os.path.abspath(dirname)): + for file_name in files: + filename = os.path.join(root, file_name) + extension = os.path.splitext(filename)[1] + if extension.lower() in ['.ttf']: + if file_name==font_file: + return True + return False + +def SetCustomFonts(rmldoc): + for name, font, fname, mode in CustomTTFonts: + if SearchFontPath(fname): + rmldoc.setTTFontMapping(name, font,filename, mode) + return True + + + +#eof diff --git a/bin/report/render/rml2pdf/trml2pdf.py b/bin/report/render/rml2pdf/trml2pdf.py index 0b50cab80cc..a9086b5b6c0 100644 --- a/bin/report/render/rml2pdf/trml2pdf.py +++ b/bin/report/render/rml2pdf/trml2pdf.py @@ -163,6 +163,26 @@ class _rml_doc(object): addMapping(name, 1, 0, name) #bold addMapping(name, 1, 1, name) #italic and bold + def setTTFontMapping(self,face, fontname,filename, mode='all'): + from reportlab.lib.fonts import addMapping + from reportlab.pdfbase import pdfmetrics + from reportlab.pdfbase.ttfonts import TTFont + + pdfmetrics.registerFont(TTFont(fontname, filename )) + if (mode == 'all'): + addMapping(face, 0, 0, fontname) #normal + addMapping(face, 0, 1, fontname) #italic + addMapping(face, 1, 0, fontname) #bold + addMapping(face, 1, 1, fontname) #italic and bold + elif (mode== 'normal') or (mode == 'regular'): + addMapping(face, 0, 0, fontname) #normal + elif (mode == 'italic'): + addMapping(face, 0, 1, fontname) #italic + elif (mode == 'bold'): + addMapping(face, 1, 0, fontname) #bold + elif (mode == 'bolditalic'): + addMapping(face, 1, 1, fontname) #italic and bold + def _textual_image(self, node): rc = '' for n in node.getchildren(): @@ -396,6 +416,11 @@ class _rml_canvas(object): self.path.close() self.canvas.drawPath(self.path, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'})) + def setFont(self, node): + from reportlab.pdfbase import pdfmetrics + if node.get('name') in pdfmetrics.standardFonts: + return self.canvas.setFont(node.get('name'), utils.unit_get(node.get('size'))) + def render(self, node): tags = { 'drawCentredString': self._drawCenteredString, @@ -408,7 +433,7 @@ class _rml_canvas(object): 'curves': self._curves, 'fill': lambda node: self.canvas.setFillColor(color.get(node.get('color'))), 'stroke': lambda node: self.canvas.setStrokeColor(color.get(node.get('color'))), - 'setFont': lambda node: self.canvas.setFont(node.get('name'), utils.unit_get(node.get('size'))), + 'setFont': self.setFont , 'place': self._place, 'circle': self._circle, 'lineMode': self._line_mode, @@ -775,6 +800,12 @@ class _rml_template(object): def parseNode(rml, localcontext = {},fout=None, images={}, path='.',title=None): node = etree.XML(rml) r = _rml_doc(node, localcontext, images, path, title=title) + #try to override some font mappings + try: + from customfonts import SetCustomFonts + SetCustomFonts(r) + except: + pass fp = cStringIO.StringIO() r.render(fp) return fp.getvalue() @@ -782,6 +813,14 @@ def parseNode(rml, localcontext = {},fout=None, images={}, path='.',title=None): def parseString(rml, localcontext = {},fout=None, images={}, path='.',title=None): node = etree.XML(rml) r = _rml_doc(node, localcontext, images, path, title=title) + + #try to override some font mappings + try: + from customfonts import SetCustomFonts + SetCustomFonts(r) + except: + pass + if fout: fp = file(fout,'wb') r.render(fp) diff --git a/bin/report/render/rml2txt/__init__.py b/bin/report/render/rml2txt/__init__.py new file mode 100644 index 00000000000..410b9f53379 --- /dev/null +++ b/bin/report/render/rml2txt/__init__.py @@ -0,0 +1,26 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2008 Tiny SPRL (). All Rights Reserved +# $Id$ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## + +from rml2txt import parseString, parseNode + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: + diff --git a/bin/report/render/rml2txt/rml2txt.py b/bin/report/render/rml2txt/rml2txt.py new file mode 100755 index 00000000000..0cddacb287d --- /dev/null +++ b/bin/report/render/rml2txt/rml2txt.py @@ -0,0 +1,551 @@ +#!/bin/env python +# -*- encoding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2008 Tiny SPRL (). All Rights Reserved +# $Id$ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## + +# Copyright (C) 2005, Fabien Pinckaers, UCL, FSA +# Copyright (C) 2008, P. Christeas +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import sys +import StringIO +import copy +from lxml import etree +import base64 + +import utils + +Font_size= 10.0 + +def verbose(text): + sys.stderr.write(text+"\n"); + +class textbox(): + """A box containing plain text. + It can have an offset, in chars. + Lines can be either text strings, or textbox'es, recursively. + """ + def __init__(self,x=0, y=0): + self.posx = x + self.posy = y + self.lines = [] + self.curline = '' + self.endspace = False + + def newline(self): + if isinstance(self.curline, textbox): + self.lines.extend(self.curline.renderlines()) + else: + self.lines.append(self.curline) + self.curline = '' + + def fline(self): + if isinstance(self.curline, textbox): + self.lines.extend(self.curline.renderlines()) + elif len(self.curline): + self.lines.append(self.curline) + self.curline = '' + + def appendtxt(self,txt): + """Append some text to the current line. + Mimic the HTML behaviour, where all whitespace evaluates to + a single space """ + if not txt: + return + bs = es = False + if txt[0].isspace(): + bs = True + if txt[len(txt)-1].isspace(): + es = True + if bs and not self.endspace: + self.curline += " " + self.curline += txt.strip().replace("\n"," ").replace("\t"," ") + if es: + self.curline += " " + self.endspace = es + + def rendertxt(self,xoffset=0): + result = '' + lineoff = "" + for i in range(self.posy): + result +="\n" + for i in range(self.posx+xoffset): + lineoff+=" " + for l in self.lines: + result+= lineoff+ l +"\n" + return result + + def renderlines(self,pad=0): + """Returns a list of lines, from the current object + pad: all lines must be at least pad characters. + """ + result = [] + lineoff = "" + for i in range(self.posx): + lineoff+=" " + for l in self.lines: + lpad = "" + if pad and len(l) < pad : + for i in range(pad - len(l)): + lpad += " " + #elif pad and len(l) > pad ? + result.append(lineoff+ l+lpad) + return result + + + def haplines(self,arr,offset,cc= ''): + """ Horizontaly append lines + """ + while (len(self.lines) < len(arr)): + self.lines.append("") + + for i in range(len(self.lines)): + while (len(self.lines[i]) < offset): + self.lines[i] += " " + for i in range(len(arr)): + self.lines[i] += cc +arr[i] + + +class _flowable(object): + def __init__(self, template, doc,localcontext): + self._tags = { + '1title': self._tag_title, + '1spacer': self._tag_spacer, + 'para': self._tag_para, + 'font': self._tag_font, + 'section': self._tag_section, + '1nextFrame': self._tag_next_frame, + 'blockTable': self._tag_table, + '1pageBreak': self._tag_page_break, + '1setNextTemplate': self._tag_next_template, + } + self.template = template + self.doc = doc + self.localcontext = localcontext + self.nitags = [] + self.tbox = None + + def warn_nitag(self,tag): + if tag not in self.nitags: + verbose("Unknown tag \"%s\", please implement it." % tag) + self.nitags.append(tag) + + def _tag_page_break(self, node): + return "\f" + + def _tag_next_template(self, node): + return '' + + def _tag_next_frame(self, node): + result=self.template.frame_stop() + result+='\n' + result+=self.template.frame_start() + return result + + def _tag_title(self, node): + node.tagName='h1' + return node.toxml() + + def _tag_spacer(self, node): + length = 1+int(utils.unit_get(node.get('length')))/35 + return "\n"*length + + def _tag_table(self, node): + self.tb.fline() + saved_tb = self.tb + self.tb = None + sizes = None + if node.get('colWidths'): + sizes = map(lambda x: utils.unit_get(x), node.get('colWidths').split(',')) + trs = [] + for n in utils._child_get(node,self): + if n.tag == 'tr': + tds = [] + for m in utils._child_get(n,self): + if m.tag == 'td': + self.tb = textbox() + self.rec_render_cnodes(m) + tds.append(self.tb) + self.tb = None + if len(tds): + trs.append(tds) + + if not sizes: + verbose("computing table sizes..") + for tds in trs: + trt = textbox() + off=0 + for i in range(len(tds)): + p = int(sizes[i]/Font_size) + trl = tds[i].renderlines(pad=p) + trt.haplines(trl,off) + off += sizes[i]/Font_size + saved_tb.curline = trt + saved_tb.fline() + + self.tb = saved_tb + return + + def _tag_para(self, node): + #TODO: styles + self.rec_render_cnodes(node) + self.tb.newline() + + def _tag_section(self, node): + #TODO: styles + self.rec_render_cnodes(node) + self.tb.newline() + + def _tag_font(self, node): + """We do ignore fonts..""" + self.rec_render_cnodes(node) + + def rec_render_cnodes(self,node): + self.tb.appendtxt(utils._process_text(self, node.text or '')) + for n in utils._child_get(node,self): + self.rec_render(n) + self.tb.appendtxt(utils._process_text(self, node.tail or '')) + + def rec_render(self,node): + """ Recursive render: fill outarr with text of current node + """ + if node.tag != None: + if node.tag in self._tags: + self._tags[node.tag](node) + else: + self.warn_nitag(node.tag) + + def render(self, node): + self.tb= textbox() + #result = self.template.start() + #result += self.template.frame_start() + self.rec_render_cnodes(node) + #result += self.template.frame_stop() + #result += self.template.end() + result = self.tb.rendertxt() + del self.tb + return result + +class _rml_tmpl_tag(object): + def __init__(self, *args): + pass + def tag_start(self): + return '' + def tag_end(self): + return False + def tag_stop(self): + return '' + def tag_mergeable(self): + return True + +class _rml_tmpl_frame(_rml_tmpl_tag): + def __init__(self, posx, width): + self.width = width + self.posx = posx + def tag_start(self): + return "frame start" + return '
 ' % (self.width+self.posx,self.posx) + def tag_end(self): + return True + def tag_stop(self): + return "frame stop" + return '

' + def tag_mergeable(self): + return False + + # An awfull workaround since I don't really understand the semantic behind merge. + def merge(self, frame): + pass + +class _rml_tmpl_draw_string(_rml_tmpl_tag): + def __init__(self, node, style): + self.posx = utils.unit_get(node.get('x')) + self.posy = utils.unit_get(node.get('y')) + aligns = { + 'drawString': 'left', + 'drawRightString': 'right', + 'drawCentredString': 'center' + } + align = aligns[node.localName] + self.pos = [(self.posx, self.posy, align, utils.text_get(node), style.get('td'), style.font_size_get('td'))] + + def tag_start(self): + return "draw string \"%s\" @(%d,%d)..\n" %("txt",self.posx,self.posy) + self.pos.sort() + res = '\\table ...' + posx = 0 + i = 0 + for (x,y,align,txt, style, fs) in self.pos: + if align=="left": + pos2 = len(txt)*fs + res+='%s' % (x - posx, style, pos2, txt) + posx = x+pos2 + if align=="right": + res+='%s' % (x - posx, style, txt) + posx = x + if align=="center": + res+='%s' % ((x - posx)*2, style, txt) + posx = 2*x-posx + i+=1 + res+='\\table end' + return res + def merge(self, ds): + self.pos+=ds.pos + +class _rml_tmpl_draw_lines(_rml_tmpl_tag): + def __init__(self, node, style): + coord = [utils.unit_get(x) for x in utils.text_get(node).split(' ')] + self.ok = False + self.posx = coord[0] + self.posy = coord[1] + self.width = coord[2]-coord[0] + self.ok = coord[1]==coord[3] + self.style = style + self.style = style.get('hr') + + def tag_start(self): + return "draw lines..\n" + if self.ok: + return '

' % (self.posx+self.width,self.posx,self.style) + else: + return '' + +class _rml_stylesheet(object): + def __init__(self, stylesheet, doc): + self.doc = doc + self.attrs = {} + self._tags = { + 'fontSize': lambda x: ('font-size',str(utils.unit_get(x))+'px'), + 'alignment': lambda x: ('text-align',str(x)) + } + result = '' + for ps in stylesheet.findall('paraStyle'): + attr = {} + attrs = ps.attributes + for i in range(attrs.length): + name = attrs.item(i).localName + attr[name] = ps.get(name) + attrs = [] + for a in attr: + if a in self._tags: + attrs.append("%s:%s" % self._tags[a](attr[a])) + if len(attrs): + result += "p."+attr['name']+" {"+'; '.join(attrs)+"}\n" + self.result = result + + def render(self): + return '' + +class _rml_draw_style(object): + def __init__(self): + self.style = {} + self._styles = { + 'fill': lambda x: {'td': {'color':x.get('color')}}, + 'setFont': lambda x: {'td': {'font-size':x.get('size')+'px'}}, + 'stroke': lambda x: {'hr': {'color':x.get('color')}}, + } + def update(self, node): + if node.localName in self._styles: + result = self._styles[node.localName](node) + for key in result: + if key in self.style: + self.style[key].update(result[key]) + else: + self.style[key] = result[key] + def font_size_get(self,tag): + size = utils.unit_get(self.style.get('td', {}).get('font-size','16')) + return size + + def get(self,tag): + if not tag in self.style: + return "" + return ';'.join(['%s:%s' % (x[0],x[1]) for x in self.style[tag].items()]) + +class _rml_template(object): + def __init__(self, localcontext, out, node, doc, images={}, path='.', title=None): + self.localcontext = localcontext + self.frame_pos = -1 + self.frames = [] + self.template_order = [] + self.page_template = {} + self.loop = 0 + self._tags = { + 'drawString': _rml_tmpl_draw_string, + 'drawRightString': _rml_tmpl_draw_string, + 'drawCentredString': _rml_tmpl_draw_string, + 'lines': _rml_tmpl_draw_lines + } + self.style = _rml_draw_style() + for pt in node.findall('pageTemplate'): + frames = {} + id = pt.get('id') + self.template_order.append(id) + for tmpl in pt.findall('frame'): + posy = int(utils.unit_get(tmpl.get('y1'))) #+utils.unit_get(tmpl.get('height'))) + posx = int(utils.unit_get(tmpl.get('x1'))) + frames[(posy,posx,tmpl.get('id'))] = _rml_tmpl_frame(posx, utils.unit_get(tmpl.get('width'))) + for tmpl in node.findall('pageGraphics'): + for n in tmpl.getchildren(): + if n.nodeType==n.ELEMENT_NODE: + if n.localName in self._tags: + t = self._tags[n.localName](n, self.style) + frames[(t.posy,t.posx,n.localName)] = t + else: + self.style.update(n) + keys = frames.keys() + keys.sort() + keys.reverse() + self.page_template[id] = [] + for key in range(len(keys)): + if key>0 and keys[key-1][0] == keys[key][0]: + if type(self.page_template[id][-1]) == type(frames[keys[key]]): + if self.page_template[id][-1].tag_mergeable(): + self.page_template[id][-1].merge(frames[keys[key]]) + continue + self.page_template[id].append(frames[keys[key]]) + self.template = self.template_order[0] + + def _get_style(self): + return self.style + + def set_next_template(self): + self.template = self.template_order[(self.template_order.index(name)+1) % self.template_order] + self.frame_pos = -1 + + def set_template(self, name): + self.template = name + self.frame_pos = -1 + + def frame_start(self): + result = '' + frames = self.page_template[self.template] + ok = True + while ok: + self.frame_pos += 1 + if self.frame_pos>=len(frames): + self.frame_pos=0 + self.loop=1 + ok = False + continue + f = frames[self.frame_pos] + result+=f.tag_start() + ok = not f.tag_end() + if ok: + result+=f.tag_stop() + return result + + def frame_stop(self): + frames = self.page_template[self.template] + f = frames[self.frame_pos] + result=f.tag_stop() + return result + + def start(self): + return '' + + def end(self): + return "template end\n" + result = '' + while not self.loop: + result += self.frame_start() + result += self.frame_stop() + return result + +class _rml_doc(object): + def __init__(self, node, localcontext, images={}, path='.', title=None): + self.localcontext = localcontext + self.etree = node + self.filename = self.etree.get('filename') + self.result = '' + + def render(self, out): + #el = self.etree.findall('docinit') + #if el: + #self.docinit(el) + + #el = self.etree.findall('stylesheet') + #self.styles = _rml_styles(el,self.localcontext) + + el = self.etree.findall('template') + self.result ="" + if len(el): + pt_obj = _rml_template(self.localcontext, out, el[0], self) + stories = utils._child_get(self.etree, self, 'story') + for story in stories: + if self.result: + self.result += '\f' + f = _flowable(pt_obj,story,self.localcontext) + self.result += f.render(story) + del f + else: + self.result = "" + self.result += '\n' + out.write( self.result) + +def parseNode(rml, localcontext = {},fout=None, images={}, path='.',title=None): + node = etree.XML(rml) + r = _rml_doc(node, localcontext, images, path, title=title) + fp = StringIO.StringIO() + r.render(fp) + return fp.getvalue() + +def parseString(rml, localcontext = {},fout=None, images={}, path='.',title=None): + node = etree.XML(rml) + r = _rml_doc(node, localcontext, images, path, title=title) + if fout: + fp = file(fout,'wb') + r.render(fp) + fp.close() + return fout + else: + fp = StringIO.StringIO() + r.render(fp) + return fp.getvalue() + +def trml2pdf_help(): + print 'Usage: rml2txt input.rml >output.html' + print 'Render the standard input (RML) and output an TXT file' + sys.exit(0) + +if __name__=="__main__": + if len(sys.argv)>1: + if sys.argv[1]=='--help': + trml2pdf_help() + print parseString(file(sys.argv[1], 'r').read()).encode('iso8859-7') + else: + print 'Usage: trml2txt input.rml >output.pdf' + print 'Try \'trml2txt --help\' for more information.' + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: + diff --git a/bin/report/render/rml2txt/utils.py b/bin/report/render/rml2txt/utils.py new file mode 100644 index 00000000000..789d79f13cf --- /dev/null +++ b/bin/report/render/rml2txt/utils.py @@ -0,0 +1,149 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). All Rights Reserved +# $Id$ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## + +# trml2pdf - An RML to PDF converter +# Copyright (C) 2003, Fabien Pinckaers, UCL, FSA +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import re +import reportlab +import reportlab.lib.units +from lxml import etree + +_regex = re.compile('\[\[(.+?)\]\]') + +def _child_get(node, self=None, tagname=None): + for n in node: + if self and self.localcontext and n.get('rml_loop', False): + oldctx = self.localcontext + for ctx in eval(n.get('rml_loop'),{}, self.localcontext): + self.localcontext.update(ctx) + if (tagname is None) or (n.tag==tagname): + if n.get('rml_except', False): + try: + eval(n.get('rml_except'), {}, self.localcontext) + except: + continue + if n.get('rml_tag'): + try: + (tag,attr) = eval(n.get('rml_tag'),{}, self.localcontext) + n2 = copy.copy(n) + n2.tag = tag + n2.attrib.update(attr) + yield n2 + except: + yield n + else: + yield n + self.localcontext = oldctx + continue + if self and self.localcontext and n.get('rml_except', False): + try: + eval(n.get('rml_except'), {}, self.localcontext) + except: + continue + if (tagname is None) or (n.tag==tagname): + yield n + +def _process_text(self, txt): + if not self.localcontext: + return txt + if not txt: + return '' + result = '' + sps = _regex.split(txt) + while sps: + # This is a simple text to translate + result += self.localcontext.get('translate', lambda x:x)(sps.pop(0)) + if sps: + try: + txt2 = eval(sps.pop(0),self.localcontext) + except: + txt2 = '' + if type(txt2) == type(0) or type(txt2) == type(0.0): + txt2 = str(txt2) + if type(txt2)==type('') or type(txt2)==type(u''): + result += txt2 + return result + +def text_get(node): + rc = '' + for node in node.getchildren(): + rc = rc + node.text + return rc + +units = [ + (re.compile('^(-?[0-9\.]+)\s*in$'), reportlab.lib.units.inch), + (re.compile('^(-?[0-9\.]+)\s*cm$'), reportlab.lib.units.cm), + (re.compile('^(-?[0-9\.]+)\s*mm$'), reportlab.lib.units.mm), + (re.compile('^(-?[0-9\.]+)\s*$'), 1) +] + +def unit_get(size): + global units + if size: + for unit in units: + res = unit[0].search(size, 0) + if res: + return unit[1]*float(res.group(1)) + return False + +def tuple_int_get(node, attr_name, default=None): + if not node.get(attr_name): + return default + res = [int(x) for x in node.get(attr_name).split(',')] + return res + +def bool_get(value): + return (str(value)=="1") or (value.lower()=='yes') + +def attr_get(node, attrs, dict={}): + res = {} + for name in attrs: + if node.get(name): + res[name] = unit_get(node.get(name)) + for key in dict: + if node.get(key): + if dict[key]=='str': + res[key] = str(node.get(key)) + elif dict[key]=='bool': + res[key] = bool_get(node.get(key)) + elif dict[key]=='int': + res[key] = int(node.get(key)) + elif dict[key]=='unit': + res[key] = unit_get(node.get(key)) + return res + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file diff --git a/bin/report/render/simple.py b/bin/report/render/simple.py index dd1d4b3ea1f..4ce10991924 100644 --- a/bin/report/render/simple.py +++ b/bin/report/render/simple.py @@ -67,7 +67,8 @@ class simple(render.render): if __name__=='__main__': import time - s = simple(''' + s = simple() + s.xml = ''' Fabien Pinckaers @@ -79,8 +80,9 @@ if __name__=='__main__': No other - ''') - print s.render() + ''' + if s.render(): + print s.get() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/bin/report/report_sxw.py b/bin/report/report_sxw.py index a853df6545c..f1df81e60bc 100644 --- a/bin/report/report_sxw.py +++ b/bin/report/report_sxw.py @@ -354,14 +354,14 @@ class report_sxw(report_rml, preprocess.report): report_type = report_xml.report_type if report_type in ['sxw','odt']: fnct = self.create_source_odt - elif report_type in ['pdf','raw','html']: + elif report_type in ['pdf','raw','txt','html']: fnct = self.create_source_pdf elif report_type=='html2html': fnct = self.create_source_html2html elif report_type=='mako2html': fnct = self.create_source_mako2html else: - raise 'Unknown Report Type' + raise Exception('Unknown Report Type: '+report_type) return fnct(cr, uid, ids, data, report_xml, context) def create_source_odt(self, cr, uid, ids, data, report_xml, context=None): diff --git a/bin/service/http_server.py b/bin/service/http_server.py new file mode 100644 index 00000000000..f9d35ed2ef7 --- /dev/null +++ b/bin/service/http_server.py @@ -0,0 +1,319 @@ +# -*- encoding: utf-8 -*- + +# +# Copyright P. Christeas 2008,2009 +# +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +############################################################################### + +""" This file contains instance of the http server. + + +""" +from websrv_lib import * +import netsvc +import threading +import tools +import os +import socket +import xmlrpclib + +from SimpleXMLRPCServer import SimpleXMLRPCDispatcher + +try: + import fcntl +except ImportError: + fcntl = None + +try: + from ssl import SSLError +except ImportError: + class SSLError(Exception): pass + +class ThreadedHTTPServer(ConnThreadingMixIn, SimpleXMLRPCDispatcher, HTTPServer): + """ A threaded httpd server, with all the necessary functionality for us. + + It also inherits the xml-rpc dispatcher, so that some xml-rpc functions + will be available to the request handler + """ + encoding = None + allow_none = False + allow_reuse_address = 1 + _send_traceback_header = False + i = 0 + + def __init__(self, addr, requestHandler, + logRequests=True, allow_none=False, encoding=None, bind_and_activate=True): + self.logRequests = logRequests + + SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding) + HTTPServer.__init__(self, addr, requestHandler) + + # [Bug #1222790] If possible, set close-on-exec flag; if a + # method spawns a subprocess, the subprocess shouldn't have + # the listening socket open. + if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'): + flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD) + flags |= fcntl.FD_CLOEXEC + fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags) + + def handle_error(self, request, client_address): + """ Override the error handler + """ + import traceback + netsvc.Logger().notifyChannel("init", netsvc.LOG_ERROR,"Server error in request from %s:\n%s" % + (client_address,traceback.format_exc())) + +class MultiHandler2(MultiHTTPHandler): + def log_message(self, format, *args): + netsvc.Logger().notifyChannel('http',netsvc.LOG_DEBUG,format % args) + + def log_error(self, format, *args): + netsvc.Logger().notifyChannel('http',netsvc.LOG_ERROR,format % args) + + +class SecureMultiHandler2(SecureMultiHTTPHandler): + def log_message(self, format, *args): + netsvc.Logger().notifyChannel('https',netsvc.LOG_DEBUG,format % args) + + def getcert_fnames(self): + tc = tools.config + fcert = tc.get_misc('httpsd','sslcert', 'ssl/server.cert') + fkey = tc.get_misc('httpsd','sslkey', 'ssl/server.key') + return (fcert,fkey) + + def log_message(self, format, *args): + netsvc.Logger().notifyChannel('http',netsvc.LOG_DEBUG,format % args) + + def log_error(self, format, *args): + netsvc.Logger().notifyChannel('http',netsvc.LOG_ERROR,format % args) + +class HttpDaemon(threading.Thread, netsvc.Server): + def __init__(self, interface, port): + threading.Thread.__init__(self) + netsvc.Server.__init__(self) + self.__port = port + self.__interface = interface + + try: + self.server = ThreadedHTTPServer((interface, port), MultiHandler2) + self.server.vdirs = [] + self.server.logRequests = True + netsvc.Logger().notifyChannel("web-services", netsvc.LOG_INFO, + "starting HTTP service at %s port %d" % (interface or '0.0.0.0', port,)) + except Exception, e: + netsvc.Logger().notifyChannel('httpd', netsvc.LOG_CRITICAL, "Error occur when starting the server daemon: %s" % (e,)) + raise + + + def attach(self, path, gw): + pass + + def stop(self): + self.running = False + if os.name != 'nt': + self.server.socket.shutdown( hasattr(socket, 'SHUT_RDWR') and socket.SHUT_RDWR or 2 ) + self.server.socket.close() + + def run(self): + #self.server.register_introspection_functions() + + self.running = True + while self.running: + self.server.handle_request() + return True + +class HttpSDaemon(threading.Thread, netsvc.Server): + def __init__(self, interface, port): + threading.Thread.__init__(self) + netsvc.Server.__init__(self) + self.__port = port + self.__interface = interface + + try: + self.server = ThreadedHTTPServer((interface, port), SecureMultiHandler2) + self.server.vdirs = [] + self.server.logRequests = True + netsvc.Logger().notifyChannel("web-services", netsvc.LOG_INFO, + "starting HTTPS service at %s port %d" % (interface or '0.0.0.0', port,)) + except SSLError, e: + netsvc.Logger().notifyChannel('httpd-ssl', netsvc.LOG_CRITICAL, "Can not load the certificate and/or the private key files") + raise + except Exception, e: + netsvc.Logger().notifyChannel('httpd-ssl', netsvc.LOG_CRITICAL, "Error occur when starting the server daemon: %s" % (e,)) + raise + + def attach(self, path, gw): + pass + + def stop(self): + self.running = False + if os.name != 'nt': + self.server.socket.shutdown( hasattr(socket, 'SHUT_RDWR') and socket.SHUT_RDWR or 2 ) + self.server.socket.close() + + def run(self): + #self.server.register_introspection_functions() + + self.running = True + while self.running: + self.server.handle_request() + return True + +httpd = None +httpsd = None + +def init_servers(): + global httpd, httpsd + if tools.config.get_misc('httpd','enable', True): + httpd = HttpDaemon(tools.config.get_misc('httpd','interface', ''), \ + tools.config.get_misc('httpd','port', 8069)) + + if tools.config.get_misc('httpsd','enable', False): + httpsd = HttpSDaemon(tools.config.get_misc('httpsd','interface', ''), \ + tools.config.get_misc('httpsd','port', 8071)) + +def reg_http_service(hts, secure_only = False): + """ Register some handler to httpd. + hts must be an HTTPDir + """ + global httpd, httpsd + if not isinstance(hts, HTTPDir): + raise Exception("Wrong class for http service") + + if httpd and not secure_only: + httpd.server.vdirs.append(hts) + + if httpsd: + httpsd.server.vdirs.append(hts) + + if (not httpd) and (not httpsd): + netsvc.Logger().notifyChannel('httpd',netsvc.LOG_WARNING,"No httpd available to register service %s" % hts.path) + return + +import SimpleXMLRPCServer +class XMLRPCRequestHandler(netsvc.OpenERPDispatcher,FixSendError,SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): + rpc_paths = [] + protocol_version = 'HTTP/1.1' + def _dispatch(self, method, params): + try: + service_name = self.path.split("/")[-1] + return self.dispatch(service_name, method, params) + except netsvc.OpenERPDispatcherException, e: + raise xmlrpclib.Fault(tools.exception_to_unicode(e.exception), e.traceback) + + def log_message(self, format, *args): + netsvc.Logger().notifyChannel('xmlrpc',netsvc.LOG_DEBUG_RPC,format % args) + + def handle(self): + pass + + def finish(self): + pass + + def setup(self): + self.connection = dummyconn() + if not len(XMLRPCRequestHandler.rpc_paths): + XMLRPCRequestHandler.rpc_paths = map(lambda s: '/%s' % s, netsvc.ExportService._services.keys()) + pass + + +def init_xmlrpc(): + if not tools.config.get_misc('xmlrpc','enable', True): + return + reg_http_service(HTTPDir('/xmlrpc/',XMLRPCRequestHandler)) + # Example of http file serving: + # reg_http_service(HTTPDir('/test/',HTTPHandler)) + netsvc.Logger().notifyChannel("web-services", netsvc.LOG_INFO, + "Registered XML-RPC over HTTP") + + +class OerpAuthProxy(AuthProxy): + """ Require basic authentication.. + + This is a copy of the BasicAuthProxy, which however checks/caches the db + as well. + """ + def __init__(self,provider): + AuthProxy.__init__(self,provider) + self.auth_creds = {} + self.auth_tries = 0 + self.last_auth = None + + def checkRequest(self,handler,path = '/'): + if self.auth_creds: + return True + auth_str = handler.headers.get('Authorization',False) + try: + db = handler.get_db_from_path(path) + print "Got db:",db + except: + if path.startswith('/'): + path = path[1:] + psp= path.split('/') + if len(psp)>1: + db = psp[0] + else: + #FIXME! + self.provider.log("Wrong path: %s, failing auth" %path) + raise AuthRejectedExc("Authorization failed. Wrong sub-path.") + + if auth_str and auth_str.startswith('Basic '): + auth_str=auth_str[len('Basic '):] + (user,passwd) = base64.decodestring(auth_str).split(':') + self.provider.log("Found user=\"%s\", passwd=\"***\" for db=\"%s\"" %(user,db)) + acd = self.provider.authenticate(db,user,passwd,handler.client_address) + if acd != False: + self.auth_creds[db] = acd + self.last_auth=db + return True + if self.auth_tries > 5: + self.provider.log("Failing authorization after 5 requests w/o password") + raise AuthRejectedExc("Authorization failed.") + self.auth_tries += 1 + raise AuthRequiredExc(atype = 'Basic', realm=self.provider.realm) + +import security +class OpenERPAuthProvider(AuthProvider): + def __init__(self,realm = 'OpenERP User'): + self.realm = realm + + def setupAuth(self, multi, handler): + if not multi.sec_realms.has_key(self.realm): + multi.sec_realms[self.realm] = OerpAuthProxy(self) + handler.auth_proxy = multi.sec_realms[self.realm] + + def authenticate(self, db, user, passwd, client_address): + try: + uid = security.login(db,user,passwd) + if uid is False: + return False + return (user, passwd, db, uid) + except Exception,e: + netsvc.Logger().notifyChannel("auth",netsvc.LOG_DEBUG,"Fail auth:"+ str(e)) + return False + + def log(self, msg): + netsvc.Logger().notifyChannel("auth",netsvc.LOG_INFO,msg) + +#eof diff --git a/bin/service/netrpc_server.py b/bin/service/netrpc_server.py new file mode 100644 index 00000000000..a126eb77907 --- /dev/null +++ b/bin/service/netrpc_server.py @@ -0,0 +1,163 @@ +# -*- encoding: utf-8 -*- + +# +# Copyright P. Christeas 2008,2009 +# Copyright (C) 2004-2009 Tiny SPRL (). All Rights Reserved +# $Id$ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## + +""" This file contains instance of the net-rpc server + + +""" +import netsvc +import threading +import tools +import os +import socket + +import tiny_socket +class TinySocketClientThread(threading.Thread, netsvc.OpenERPDispatcher): + def __init__(self, sock, threads): + threading.Thread.__init__(self) + self.sock = sock + # Only at the server side, use a big timeout: close the + # clients connection when they're idle for 20min. + self.sock.settimeout(1200) + self.threads = threads + + def __del__(self): + if self.sock: + try: + if hasattr(socket, 'SHUT_RDWR'): + self.socket.shutdown(socket.SHUT_RDWR) + else: + self.socket.shutdown(2) + except: pass + # That should garbage-collect and close it, too + self.sock = None + + def run(self): + # import select + self.running = True + try: + ts = tiny_socket.mysocket(self.sock) + except: + self.threads.remove(self) + self.running = False + return False + while self.running: + try: + msg = ts.myreceive() + except: + self.threads.remove(self) + self.running = False + return False + try: + result = self.dispatch(msg[0], msg[1], msg[2:]) + ts.mysend(result) + except netsvc.OpenERPDispatcherException, e: + try: + new_e = Exception(tools.exception_to_unicode(e.exception)) # avoid problems of pickeling + ts.mysend(new_e, exception=True, traceback=e.traceback) + except: + self.running = False + break + except Exception, e: + # this code should not be reachable, therefore we warn + netsvc.Logger().notifyChannel("net-rpc", netsvc.LOG_WARNING, "exception: %" % str(e)) + break + + self.threads.remove(self) + self.running = False + return True + + def stop(self): + self.running = False + + +class TinySocketServerThread(threading.Thread,netsvc.Server): + def __init__(self, interface, port, secure=False): + threading.Thread.__init__(self, name="Net-RPC socket") + netsvc.Server.__init__(self) + self.__port = port + self.__interface = interface + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.socket.bind((self.__interface, self.__port)) + self.socket.listen(5) + self.threads = [] + netsvc.Logger().notifyChannel("web-services", netsvc.LOG_INFO, + "starting NET-RPC service at %s port %d" % (interface or '0.0.0.0', port,)) + + def run(self): + # import select + try: + self.running = True + while self.running: + (clientsocket, address) = self.socket.accept() + ct = TinySocketClientThread(clientsocket, self.threads) + clientsocket = None + self.threads.append(ct) + ct.start() + lt = len(self.threads) + if (lt > 10) and (lt % 10 == 0): + # Not many threads should be serving at the same time, so log + # their abuse. + netsvc.Logger().notifyChannel("web-services", netsvc.LOG_DEBUG, + "Netrpc: %d threads" % len(self.threads)) + self.socket.close() + except Exception, e: + netsvc.Logger().notifyChannel("web-services", netsvc.LOG_WARNING, + "Netrpc: closing because of exception %s" % str(e)) + self.socket.close() + return False + + def stop(self): + self.running = False + for t in self.threads: + t.stop() + try: + if hasattr(socket, 'SHUT_RDWR'): + self.socket.shutdown(socket.SHUT_RDWR) + else: + self.socket.shutdown(2) + self.socket.close() + except: + return False + + def stats(self): + res = "Net-RPC: " + ( (self.running and "running") or "stopped") + i = 0 + for t in self.threads: + i += 1 + res += "\nNet-RPC #%d: %s " % (i, t.name) + if t.isAlive(): + res += "running" + else: + res += "finished" + if t.sock: + res += ", socket" + return res + +netrpcd = None + +def init_servers(): + global netrpcd + if tools.config.get_misc('netrpcd','enable', True): + netrpcd = TinySocketServerThread(tools.config.get_misc('netrpcd','interface', ''), \ + tools.config.get_misc('netrpcd','port', 8070)) diff --git a/bin/service/security.py b/bin/service/security.py index 3ff9b90a317..23c67c9b7e1 100644 --- a/bin/service/security.py +++ b/bin/service/security.py @@ -24,6 +24,13 @@ import tools _uid_cache = {} +# When rejecting a password, we need to give as little info as possible +class ExceptionNoTb(Exception): + def __init__(self, msg ): + # self.message = msg # No need in Python 2.6 + self.traceback = ('','','') + self.args = (msg, '') + def login(db, login, password): cr = pooler.get_db(db).cursor() if password: @@ -41,7 +48,7 @@ def check_super(passwd): if passwd == tools.config['admin_passwd']: return True else: - raise Exception('AccessDenied') + raise ExceptionNoTb('AccessDenied') def check(db, uid, passwd): cached_pass = _uid_cache.get(db, {}).get(uid) @@ -55,7 +62,7 @@ def check(db, uid, passwd): res = cr.fetchone()[0] cr.close() if not bool(res): - raise Exception('AccessDenied') + raise ExceptionNoTb('AccessDenied') if res: if _uid_cache.has_key(db): ulist = _uid_cache[db] @@ -73,8 +80,9 @@ def access(db, uid, passwd, sec_level, ids): res = cr.fetchone() cr.close() if not res: - raise Exception('Bad username or password') + raise ExceptionNoTb('Bad username or password') return res[0] + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/bin/service/web_services.py b/bin/service/web_services.py index a71e4ea379a..c9113cc097e 100644 --- a/bin/service/web_services.py +++ b/bin/service/web_services.py @@ -40,29 +40,36 @@ import tools import locale logging.basicConfig() -class db(netsvc.Service): +class db(netsvc.ExportService): def __init__(self, name="db"): - netsvc.Service.__init__(self, name) + netsvc.ExportService.__init__(self, name) self.joinGroup("web-services") - self.exportMethod(self.create) - self.exportMethod(self.get_progress) - self.exportMethod(self.drop) - self.exportMethod(self.dump) - self.exportMethod(self.restore) - self.exportMethod(self.rename) - self.exportMethod(self.list) - self.exportMethod(self.list_lang) - self.exportMethod(self.change_admin_password) - self.exportMethod(self.server_version) - self.exportMethod(self.migrate_databases) self.actions = {} self.id = 0 self.id_protect = threading.Semaphore() self._pg_psw_env_var_is_set = False # on win32, pg_dump need the PGPASSWORD env var - def create(self, password, db_name, demo, lang, user_password='admin'): - security.check_super(password) + def dispatch(self, method, auth, params): + if method in [ 'create', 'get_progress', 'drop', 'dump', + 'restore', 'rename', + 'change_admin_password', 'migrate_databases' ]: + passwd = params[0] + params = params[1:] + security.check_super(passwd) + elif method in [ 'db_exist', 'list', 'list_lang', 'server_version' ]: + # params = params + # No security check for these methods + pass + else: + raise KeyError("Method not found: %s" % method) + fn = getattr(self, 'exp_'+method) + return fn(*params) + + def new_dispatch(self,method,auth,params): + pass + + def exp_create(self, db_name, demo, lang, user_password='admin'): self.id_protect.acquire() self.id += 1 id = self.id @@ -131,8 +138,7 @@ class db(netsvc.Service): self.actions[id]['thread'] = create_thread return id - def get_progress(self, password, id): - security.check_super(password) + def exp_get_progress(self, id): if self.actions[id]['thread'].isAlive(): # return addons.init_progress[db_name] return (min(self.actions[id].get('progress', 0),0.95), []) @@ -147,8 +153,7 @@ class db(netsvc.Service): del self.actions[id] raise Exception, e - def drop(self, password, db_name): - security.check_super(password) + def exp_drop(self, db_name): sql_db.close_db(db_name) logger = netsvc.Logger() @@ -178,8 +183,7 @@ class db(netsvc.Service): if os.name == 'nt' and self._pg_psw_env_var_is_set: os.environ['PGPASSWORD'] = '' - def dump(self, password, db_name): - security.check_super(password) + def exp_dump(self, db_name): logger = netsvc.Logger() self._set_pg_psw_env_var() @@ -208,8 +212,7 @@ class db(netsvc.Service): return base64.encodestring(data) - def restore(self, password, db_name, data): - security.check_super(password) + def exp_restore(self, db_name, data): logger = netsvc.Logger() self._set_pg_psw_env_var() @@ -259,8 +262,7 @@ class db(netsvc.Service): return True - def rename(self, password, old_name, new_name): - security.check_super(password) + def exp_rename(self, old_name, new_name): sql_db.close_db(old_name) logger = netsvc.Logger() @@ -284,14 +286,14 @@ class db(netsvc.Service): cr.close() return True - def db_exist(self, db_name): + def exp_db_exist(self, db_name): try: db = sql_db.db_connect(db_name) return True except: return False - def list(self): + def exp_list(self): db = sql_db.db_connect('template1') cr = db.cursor() try: @@ -319,27 +321,25 @@ class db(netsvc.Service): res.sort() return res - def change_admin_password(self, old_password, new_password): - security.check_super(old_password) + def exp_change_admin_password(self, new_password): tools.config['admin_passwd'] = new_password tools.config.save() return True - def list_lang(self): + def exp_list_lang(self): return tools.scan_languages() - def server_version(self): + def exp_server_version(self): """ Return the version of the server Used by the client to verify the compatibility with its own version """ return release.version - def migrate_databases(self, password, databases): + def exp_migrate_databases(self,databases): from osv.orm import except_orm from osv.osv import except_osv - security.check_super(password) l = netsvc.Logger() for db in databases: try: @@ -358,63 +358,74 @@ class db(netsvc.Service): return True db() -class common(netsvc.Service): +class _ObjectService(netsvc.ExportService): + "A common base class for those who have fn(db, uid, password,...) " + + def common_dispatch(self, method, auth, params): + (db, uid, passwd ) = params[0:3] + params = params[3:] + security.check(db,uid,passwd) + cr = pooler.get_db(db).cursor() + fn = getattr(self, 'exp_'+method) + res = fn(cr, uid, *params) + cr.commit() + cr.close() + return res + +class common(_ObjectService): def __init__(self,name="common"): - netsvc.Service.__init__(self,name) + _ObjectService.__init__(self,name) self.joinGroup("web-services") - self.exportMethod(self.ir_get) - self.exportMethod(self.ir_set) - self.exportMethod(self.ir_del) - self.exportMethod(self.about) - self.exportMethod(self.login) - self.exportMethod(self.logout) - self.exportMethod(self.timezone_get) - self.exportMethod(self.get_available_updates) - self.exportMethod(self.get_migration_scripts) - self.exportMethod(self.get_server_environment) - self.exportMethod(self.login_message) - def ir_set(self, db, uid, password, keys, args, name, value, replace=True, isobject=False): - security.check(db, uid, password) - cr = pooler.get_db(db).cursor() + def dispatch(self, method, auth, params): + logger = netsvc.Logger() + if method in [ 'ir_set','ir_del', 'ir_get' ]: + return self.common_dispatch(method,auth,params) + if method == 'login': + # At this old dispatcher, we do NOT update the auth proxy + res = security.login(params[0], params[1], params[2]) + msg = res and 'successful login' or 'bad login or password' + # TODO log the client ip address.. + logger.notifyChannel("web-service", netsvc.LOG_INFO, "%s from '%s' using database '%s'" % (msg, params[1], params[0].lower())) + return res or False + elif method == 'logout': + if auth: + auth.logout(params[1]) + logger.notifyChannel("web-service", netsvc.LOG_INFO,'Logout %s from database %s'%(login,db)) + return True + elif method in ['about', 'timezone_get', 'get_server_environment', 'login_message', 'get_stats' ]: + pass + elif method in ['get_available_updates', 'get_migration_scripts', 'set_loglevel']: + passwd = params[0] + params = params[1:] + security.check_super(passwd) + else: + raise Exception("Method not found: %s" % method) + + fn = getattr(self, 'exp_'+method) + return fn(*params) + + + def new_dispatch(self,method,auth,params): + pass + + def exp_ir_set(self, cr, uid, keys, args, name, value, replace=True, isobject=False): res = ir.ir_set(cr,uid, keys, args, name, value, replace, isobject) - cr.commit() - cr.close() return res - def ir_del(self, db, uid, password, id): - security.check(db, uid, password) - cr = pooler.get_db(db).cursor() + def exp_ir_del(self, cr, uid, id): res = ir.ir_del(cr,uid, id) - cr.commit() - cr.close() return res - def ir_get(self, db, uid, password, keys, args=None, meta=None, context=None): + def exp_ir_get(self, cr, uid, keys, args=None, meta=None, context=None): if not args: args=[] if not context: context={} - security.check(db, uid, password) - cr = pooler.get_db(db).cursor() res = ir.ir_get(cr,uid, keys, args, meta, context) - cr.commit() - cr.close() return res - def login(self, db, login, password): - res = security.login(db, login, password) - logger = netsvc.Logger() - msg = res and 'successful login' or 'bad login or password' - logger.notifyChannel("web-service", netsvc.LOG_INFO, "%s from '%s' using database '%s'" % (msg, login, db.lower())) - return res or False - - def logout(self, db, login, password): - logger = netsvc.Logger() - logger.notifyChannel("web-service", netsvc.LOG_INFO,'Logout %s from database %s'%(login,db)) - return True - - def about(self, extended=False): + def exp_about(self, extended=False): """Return information about the OpenERP Server. @param extended: if True then return version info @@ -434,12 +445,11 @@ GNU Public Licence. return info, release.version return info - def timezone_get(self, db, login, password): + def exp_timezone_get(self, db, login, password): return time.tzname[0] - def get_available_updates(self, password, contract_id, contract_password): - security.check_super(password) + def exp_get_available_updates(self, contract_id, contract_password): import tools.maintenance as tm try: rc = tm.remote_contract(contract_id, contract_password) @@ -452,8 +462,7 @@ GNU Public Licence. self.abortResponse(1, 'Migration Error', 'warning', str(e)) - def get_migration_scripts(self, password, contract_id, contract_password): - security.check_super(password) + def exp_get_migration_scripts(self, contract_id, contract_password): l = netsvc.Logger() import tools.maintenance as tm try: @@ -525,12 +534,7 @@ GNU Public Licence. l.notifyChannel('migration', netsvc.LOG_ERROR, tb_s) raise - def get_server_environment(self): - try: - rev_id = os.popen('bzr revision-info').read() - except Exception,e: - rev_id = 'Exception: %s\n' % (tools.ustr(e)) - + def exp_get_server_environment(self): os_lang = '.'.join( [x for x in locale.getdefaultlocale() if x] ) if not os_lang: os_lang = 'NOT SET' @@ -549,43 +553,48 @@ GNU Public Licence. 'Operating System Architecture : %s\n' \ 'Operating System Locale : %s\n'\ 'Python Version : %s\n'\ - 'OpenERP-Server Version : %s\n'\ - 'Last revision No. & ID : %s'\ + 'OpenERP-Server Version : %s'\ %(platform.release(), platform.version(), platform.architecture()[0], - os_lang, platform.python_version(),release.version,rev_id) + os_lang, platform.python_version(),release.version) return environment - def login_message(self): + def exp_login_message(self): return tools.config.get('login_message', False) + def exp_set_loglevel(self,loglevel): + l = netsvc.Logger() + l.set_loglevel(int(loglevel)) + return True + + def exp_get_stats(self): + import threading + res = "OpenERP server: %d threads\n" % threading.active_count() + res += netsvc.Server.allStats() + return res + common() -class objects_proxy(netsvc.Service): +class objects_proxy(netsvc.ExportService): def __init__(self, name="object"): - netsvc.Service.__init__(self,name) + netsvc.ExportService.__init__(self,name) self.joinGroup('web-services') - self.exportMethod(self.execute) - self.exportMethod(self.exec_workflow) - self.exportMethod(self.obj_list) - def exec_workflow(self, db, uid, passwd, object, method, id): - security.check(db, uid, passwd) - service = netsvc.LocalService("object_proxy") - res = service.exec_workflow(db, uid, object, method, id) + def dispatch(self, method, auth, params): + (db, uid, passwd ) = params[0:3] + params = params[3:] + if method not in ['execute','exec_workflow','obj_list']: + raise KeyError("Method not supported %s" % method) + security.check(db,uid,passwd) + ls = netsvc.LocalService('object_proxy') + fn = getattr(ls, method) + res = fn(db, uid, *params) return res - def execute(self, db, uid, passwd, object, method, *args): - security.check(db, uid, passwd) - service = netsvc.LocalService("object_proxy") - res = service.execute(db, uid, object, method, *args) - return res + + def new_dispatch(self,method,auth,params): + pass - def obj_list(self, db, uid, passwd): - security.check(db, uid, passwd) - service = netsvc.LocalService("object_proxy") - res = service.obj_list() - return res objects_proxy() @@ -600,26 +609,36 @@ objects_proxy() # Wizard datas: {} # TODO: change local request to OSE request/reply pattern # -class wizard(netsvc.Service): +class wizard(netsvc.ExportService): def __init__(self, name='wizard'): - netsvc.Service.__init__(self,name) + netsvc.ExportService.__init__(self,name) self.joinGroup('web-services') - self.exportMethod(self.execute) - self.exportMethod(self.create) self.id = 0 self.wiz_datas = {} self.wiz_name = {} self.wiz_uid = {} + def dispatch(self, method, auth, params): + (db, uid, passwd ) = params[0:3] + params = params[3:] + if method not in ['execute','create']: + raise KeyError("Method not supported %s" % method) + security.check(db,uid,passwd) + fn = getattr(self, 'exp_'+method) + res = fn(db, uid, *params) + return res + + def new_dispatch(self,method,auth,params): + pass + def _execute(self, db, uid, wiz_id, datas, action, context): self.wiz_datas[wiz_id].update(datas) wiz = netsvc.LocalService('wizard.'+self.wiz_name[wiz_id]) return wiz.execute(db, uid, self.wiz_datas[wiz_id], action, context) - def create(self, db, uid, passwd, wiz_name, datas=None): + def exp_create(self, db, uid, wiz_name, datas=None): if not datas: datas={} - security.check(db, uid, passwd) #FIXME: this is not thread-safe self.id += 1 self.wiz_datas[self.id] = {} @@ -627,10 +646,9 @@ class wizard(netsvc.Service): self.wiz_uid[self.id] = uid return self.id - def execute(self, db, uid, passwd, wiz_id, datas, action='init', context=None): + def exp_execute(self, db, uid, wiz_id, datas, action='init', context=None): if not context: context={} - security.check(db, uid, passwd) if wiz_id in self.wiz_uid: if self.wiz_uid[wiz_id] == uid: @@ -654,22 +672,33 @@ class ExceptionWithTraceback(Exception): self.traceback = tb self.args = (msg, tb) -class report_spool(netsvc.Service): +class report_spool(netsvc.ExportService): def __init__(self, name='report'): - netsvc.Service.__init__(self, name) + netsvc.ExportService.__init__(self, name) self.joinGroup('web-services') - self.exportMethod(self.report) - self.exportMethod(self.report_get) self._reports = {} self.id = 0 self.id_protect = threading.Semaphore() - def report(self, db, uid, passwd, object, ids, datas=None, context=None): + def dispatch(self, method, auth, params): + (db, uid, passwd ) = params[0:3] + params = params[3:] + if method not in ['report','report_get']: + raise KeyError("Method not supported %s" % method) + security.check(db,uid,passwd) + fn = getattr(self, 'exp_' + method) + res = fn(db, uid, *params) + return res + + + def new_dispatch(self,method,auth,params): + pass + + def exp_report(self, db, uid, object, ids, datas=None, context=None): if not datas: datas={} if not context: context={} - security.check(db, uid, passwd) self.id_protect.acquire() self.id += 1 @@ -725,8 +754,7 @@ class report_spool(netsvc.Service): del self._reports[report_id] return res - def report_get(self, db, uid, passwd, report_id): - security.check(db, uid, passwd) + def exp_report_get(self, db, uid, report_id): if report_id in self._reports: if self._reports[report_id]['uid'] == uid: diff --git a/bin/service/websrv_lib.py b/bin/service/websrv_lib.py new file mode 100644 index 00000000000..1913b5d5102 --- /dev/null +++ b/bin/service/websrv_lib.py @@ -0,0 +1,423 @@ +# -*- encoding: utf-8 -*- + +# +# Copyright P. Christeas 2008,2009 +# +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +############################################################################### + +""" Framework for generic http servers + +""" + +import socket +import base64 +import SocketServer +from BaseHTTPServer import * +from SimpleHTTPServer import SimpleHTTPRequestHandler + +class AuthRequiredExc(Exception): + def __init__(self,atype,realm): + Exception.__init__(self) + self.atype = atype + self.realm = realm + +class AuthRejectedExc(Exception): + pass + +class AuthProvider: + def __init__(self,realm): + self.realm = realm + + def setupAuth(self, multi,handler): + """ Attach an AuthProxy object to handler + """ + pass + + def authenticate(self, user, passwd, client_address): + return False + + def log(self, msg): + print msg + +class BasicAuthProvider(AuthProvider): + def setupAuth(self, multi, handler): + if not multi.sec_realms.has_key(self.realm): + multi.sec_realms[self.realm] = BasicAuthProxy(self) + + +class AuthProxy: + """ This class will hold authentication information for a handler, + i.e. a connection + """ + def __init__(self, provider): + self.provider = provider + + def checkRequest(self,handler,path = '/'): + """ Check if we are allowed to process that request + """ + pass + +class BasicAuthProxy(AuthProxy): + """ Require basic authentication.. + """ + def __init__(self,provider): + AuthProxy.__init__(self,provider) + self.auth_creds = None + self.auth_tries = 0 + + def checkRequest(self,handler,path = '/'): + if self.auth_creds: + return True + auth_str = handler.headers.get('Authorization',False) + if auth_str and auth_str.startswith('Basic '): + auth_str=auth_str[len('Basic '):] + (user,passwd) = base64.decodestring(auth_str).split(':') + self.provider.log("Found user=\"%s\", passwd=\"%s\"" %(user,passwd)) + self.auth_creds = self.provider.authenticate(user,passwd,handler.client_address) + if self.auth_creds: + return True + if self.auth_tries > 5: + self.provider.log("Failing authorization after 5 requests w/o password") + raise AuthRejectedExc("Authorization failed.") + self.auth_tries += 1 + raise AuthRequiredExc(atype = 'Basic', realm=self.provider.realm) + + +class HTTPHandler(SimpleHTTPRequestHandler): + def __init__(self,request, client_address, server): + SimpleHTTPRequestHandler.__init__(self,request,client_address,server) + # print "Handler for %s inited" % str(client_address) + self.protocol_version = 'HTTP/1.1' + self.connection = dummyconn() + + def handle(self): + """ Classes here should NOT handle inside their constructor + """ + pass + + def finish(self): + pass + + def setup(self): + pass + +class HTTPDir: + """ A dispatcher class, like a virtual folder in httpd + """ + def __init__(self,path,handler, auth_provider = None): + self.path = path + self.handler = handler + self.auth_provider = auth_provider + + def matches(self, request): + """ Test if some request matches us. If so, return + the matched path. """ + if request.startswith(self.path): + return self.path + return False + +class noconnection: + """ a class to use instead of the real connection + """ + def makefile(self, mode, bufsize): + return None + +class dummyconn: + def shutdown(self, tru): + pass + +def _quote_html(html): + return html.replace("&", "&").replace("<", "<").replace(">", ">") + +class FixSendError: + #error_message_format = """ """ + def send_error(self, code, message=None): + #overriden from BaseHTTPRequestHandler, we also send the content-length + try: + short, long = self.responses[code] + except KeyError: + short, long = '???', '???' + if message is None: + message = short + explain = long + self.log_error("code %d, message %s", code, message) + # using _quote_html to prevent Cross Site Scripting attacks (see bug #1100201) + content = (self.error_message_format % + {'code': code, 'message': _quote_html(message), 'explain': explain}) + self.send_response(code, message) + self.send_header("Content-Type", self.error_content_type) + self.send_header('Connection', 'close') + self.send_header('Content-Length', len(content) or 0) + self.end_headers() + if self.command != 'HEAD' and code >= 200 and code not in (204, 304): + self.wfile.write(content) + +class MultiHTTPHandler(FixSendError,BaseHTTPRequestHandler): + """ this is a multiple handler, that will dispatch each request + to a nested handler, iff it matches + + The handler will also have *one* dict of authentication proxies, + groupped by their realm. + """ + + protocol_version = "HTTP/1.1" + default_request_version = "HTTP/0.9" # compatibility with py2.5 + + auth_required_msg = """ Authorization required + You must authenticate to use this service\r\r""" + + def __init__(self, request, client_address, server): + self.in_handlers = {} + self.sec_realms = {} + SocketServer.StreamRequestHandler.__init__(self,request,client_address,server) + self.log_message("MultiHttpHandler init for %s" %(str(client_address))) + + def _handle_one_foreign(self,fore, path, auth_provider): + """ This method overrides the handle_one_request for *children* + handlers. It is required, since the first line should not be + read again.. + + """ + fore.raw_requestline = "%s %s %s\n" % (self.command, path, self.version) + if not fore.parse_request(): # An error code has been sent, just exit + return + self.request_version = fore.request_version + if auth_provider and auth_provider.realm: + try: + self.sec_realms[auth_provider.realm].checkRequest(fore,path) + except AuthRequiredExc,ae: + if self.request_version != 'HTTP/1.1': + self.log_error("Cannot require auth at %s",self.request_version) + self.send_error(401) + return + self._get_ignore_body(fore) # consume any body that came, not loose sync with input + self.send_response(401,'Authorization required') + self.send_header('WWW-Authenticate','%s realm="%s"' % (ae.atype,ae.realm)) + self.send_header('Connection', 'keep-alive') + self.send_header('Content-Type','text/html') + self.send_header('Content-Length',len(self.auth_required_msg)) + self.end_headers() + self.wfile.write(self.auth_required_msg) + return + except AuthRejectedExc,e: + self.log_error("Rejected auth: %s" % e.args[0]) + self.send_error(401,e.args[0]) + self.close_connection = 1 + return + mname = 'do_' + fore.command + if not hasattr(fore, mname): + fore.send_error(501, "Unsupported method (%r)" % fore.command) + return + fore.close_connection = 0 + method = getattr(fore, mname) + method() + if fore.close_connection: + # print "Closing connection because of handler" + self.close_connection = fore.close_connection + + def parse_rawline(self): + """Parse a request (internal). + + The request should be stored in self.raw_requestline; the results + are in self.command, self.path, self.request_version and + self.headers. + + Return True for success, False for failure; on failure, an + error is sent back. + + """ + self.command = None # set in case of error on the first line + self.request_version = version = self.default_request_version + self.close_connection = 1 + requestline = self.raw_requestline + if requestline[-2:] == '\r\n': + requestline = requestline[:-2] + elif requestline[-1:] == '\n': + requestline = requestline[:-1] + self.requestline = requestline + words = requestline.split() + if len(words) == 3: + [command, path, version] = words + if version[:5] != 'HTTP/': + self.send_error(400, "Bad request version (%r)" % version) + return False + try: + base_version_number = version.split('/', 1)[1] + version_number = base_version_number.split(".") + # RFC 2145 section 3.1 says there can be only one "." and + # - major and minor numbers MUST be treated as + # separate integers; + # - HTTP/2.4 is a lower version than HTTP/2.13, which in + # turn is lower than HTTP/12.3; + # - Leading zeros MUST be ignored by recipients. + if len(version_number) != 2: + raise ValueError + version_number = int(version_number[0]), int(version_number[1]) + except (ValueError, IndexError): + self.send_error(400, "Bad request version (%r)" % version) + return False + if version_number >= (1, 1): + self.close_connection = 0 + if version_number >= (2, 0): + self.send_error(505, + "Invalid HTTP Version (%s)" % base_version_number) + return False + elif len(words) == 2: + [command, path] = words + self.close_connection = 1 + if command != 'GET': + self.send_error(400, + "Bad HTTP/0.9 request type (%r)" % command) + return False + elif not words: + return False + else: + self.send_error(400, "Bad request syntax (%r)" % requestline) + return False + self.request_version = version + self.command, self.path, self.version = command, path, version + return True + + def handle_one_request(self): + """Handle a single HTTP request. + Dispatch to the correct handler. + """ + self.request.setblocking(True) + self.raw_requestline = self.rfile.readline() + if not self.raw_requestline: + self.close_connection = 1 + # self.log_message("no requestline, connection closed?") + return + if not self.parse_rawline(): + self.log_message("Could not parse rawline.") + return + # self.parse_request(): # Do NOT parse here. the first line should be the only + for vdir in self.server.vdirs: + p = vdir.matches(self.path) + if p == False: + continue + npath = self.path[len(p):] + if not npath.startswith('/'): + npath = '/' + npath + + if not self.in_handlers.has_key(p): + self.in_handlers[p] = vdir.handler(noconnection(),self.client_address,self.server) + if vdir.auth_provider: + vdir.auth_provider.setupAuth(self, self.in_handlers[p]) + hnd = self.in_handlers[p] + hnd.rfile = self.rfile + hnd.wfile = self.wfile + self.rlpath = self.raw_requestline + self._handle_one_foreign(hnd,npath, vdir.auth_provider) + # print "Handled, closing = ", self.close_connection + return + # if no match: + self.send_error(404, "Path not found: %s" % self.path) + return + + def _get_ignore_body(self,fore): + if not fore.headers.has_key("content-length"): + return + max_chunk_size = 10*1024*1024 + size_remaining = int(fore.headers["content-length"]) + got = '' + while size_remaining: + chunk_size = min(size_remaining, max_chunk_size) + got = fore.rfile.read(chunk_size) + size_remaining -= len(got) + + +class SecureMultiHTTPHandler(MultiHTTPHandler): + def getcert_fnames(self): + """ Return a pair with the filenames of ssl cert,key + + Override this to direct to other filenames + """ + return ('server.cert','server.key') + + def setup(self): + import ssl + certfile, keyfile = self.getcert_fnames() + try: + self.connection = ssl.wrap_socket(self.request, + server_side=True, + certfile=certfile, + keyfile=keyfile, + ssl_version=ssl.PROTOCOL_SSLv23) + self.rfile = self.connection.makefile('rb', self.rbufsize) + self.wfile = self.connection.makefile('wb', self.wbufsize) + self.log_message("Secure %s connection from %s",self.connection.cipher(),self.client_address) + except: + self.request.shutdown(socket.SHUT_RDWR) + raise + + def finish(self): + # With ssl connections, closing the filehandlers alone may not + # work because of ref counting. We explicitly tell the socket + # to shutdown. + MultiHTTPHandler.finish(self) + try: + self.connection.shutdown(socket.SHUT_RDWR) + except: + pass + +import threading +class ConnThreadingMixIn: + """Mix-in class to handle each _connection_ in a new thread. + + This is necessary for persistent connections, where multiple + requests should be handled synchronously at each connection, but + multiple connections can run in parallel. + """ + + # Decides how threads will act upon termination of the + # main process + daemon_threads = False + + def _handle_request_noblock(self): + """Start a new thread to process the request.""" + t = threading.Thread(target = self._handle_request2) + if self.daemon_threads: + t.setDaemon (1) + t.start() + + def _handle_request2(self): + """Handle one request, without blocking. + + I assume that select.select has returned that the socket is + readable before this function was called, so there should be + no risk of blocking in get_request(). + """ + try: + request, client_address = self.get_request() + except socket.error: + return + if self.verify_request(request, client_address): + try: + self.process_request(request, client_address) + except: + self.handle_error(request, client_address) + self.close_request(request) + +#eof diff --git a/bin/sql_db.py b/bin/sql_db.py index 48e0c9bb58f..f04c9682817 100644 --- a/bin/sql_db.py +++ b/bin/sql_db.py @@ -59,10 +59,12 @@ re_from = re.compile('.* from "?([a-zA-Z_0-9]+)"? .*$'); re_into = re.compile('.* into "?([a-zA-Z_0-9]+)"? .*$'); -def log(msg, lvl=netsvc.LOG_DEBUG): +def log(msg, lvl=netsvc.LOG_DEBUG2): logger = netsvc.Logger() logger.notifyChannel('sql', lvl, msg) +sql_counter = 0 + class Cursor(object): IN_MAX = 1000 @@ -70,7 +72,7 @@ class Cursor(object): @wraps(f) def wrapper(self, *args, **kwargs): if self.__closed: - raise psycopg2.ProgrammingError('Unable to use the cursor after having closing it') + raise psycopg2.ProgrammingError('Unable to use the cursor after having closed it') return f(self, *args, **kwargs) return wrapper @@ -111,7 +113,7 @@ class Cursor(object): def execute(self, query, params=None): if '%d' in query or '%f' in query: log(query, netsvc.LOG_WARNING) - log("SQL queries mustn't contain %d or %f anymore. Use only %s", netsvc.LOG_WARNING) + log("SQL queries cannot contain %d or %f anymore. Use only %s", netsvc.LOG_WARNING) if params: query = query.replace('%d', '%s').replace('%f', '%s') @@ -121,6 +123,10 @@ class Cursor(object): try: params = params or None res = self._obj.execute(query, params) + except psycopg2.ProgrammingError, pe: + logger= netsvc.Logger() + logger.notifyChannel('sql_db', netsvc.LOG_ERROR, "Programming error: %s, in query %s" % (pe, query)) + raise except Exception, e: log("bad query: %s" % self._obj.query) log(e) @@ -142,22 +148,22 @@ class Cursor(object): return res def print_log(self): + global sql_counter + sql_counter += self.sql_log_count if not self.sql_log: return - def process(type): sqllogs = {'from':self.sql_from_log, 'into':self.sql_into_log} - if not sqllogs[type]: - return - sqllogitems = sqllogs[type].items() - sqllogitems.sort(key=lambda k: k[1][1]) sum = 0 - log("SQL LOG %s:" % (type,)) - for r in sqllogitems: - log("table: %s: %s/%s" %(r[0], str(r[1][1]), r[1][0])) - sum+= r[1][1] - log("SUM:%s/%d" % (sum, self.sql_log_count)) - sqllogs[type].clear() + if sqllogs[type]: + sqllogitems = sqllogs[type].items() + sqllogitems.sort(key=lambda k: k[1][1]) + log("SQL LOG %s:" % (type,)) + for r in sqllogitems: + log("table: %s: %s/%s" %(r[0], str(r[1][1]), r[1][0])) + sum+= r[1][1] + sqllogs[type].clear() + log("SUM %s:%s/%d [%d]" % (type, sum, self.sql_log_count,sql_counter)) process('from') process('into') self.sql_log_count = 0 diff --git a/bin/ssl/cert.cfg b/bin/ssl/cert.cfg new file mode 100644 index 00000000000..8cab1eeb4ca --- /dev/null +++ b/bin/ssl/cert.cfg @@ -0,0 +1,89 @@ +# X.509 Certificate options +# +# DN options + +# The organization of the subject. +organization = "Acme inc." + +# The organizational unit of the subject. +unit = "dept." + +# The locality of the subject. +# locality = + +# The state of the certificate owner. +state = "Attiki" + +# The country of the subject. Two letter code. +country = GR + +# The common name of the certificate owner. +cn = "Some company" + +# A user id of the certificate owner. +#uid = "clauper" + +# If the supported DN OIDs are not adequate you can set +# any OID here. +# For example set the X.520 Title and the X.520 Pseudonym +# by using OID and string pairs. +#dn_oid = "2.5.4.12" "Dr." "2.5.4.65" "jackal" + +# This is deprecated and should not be used in new +# certificates. +# pkcs9_email = "none@none.org" + +# The serial number of the certificate +serial = 001 + +# In how many days, counting from today, this certificate will expire. +expiration_days = 700 + +# X.509 v3 extensions + +# A dnsname in case of a WWW server. +#dns_name = "www.none.org" +#dns_name = "www.morethanone.org" + +# An IP address in case of a server. +#ip_address = "192.168.1.1" + +# An email in case of a person +email = "none@none.org" + +# An URL that has CRLs (certificate revocation lists) +# available. Needed in CA certificates. +#crl_dist_points = "http://www.getcrl.crl/getcrl/" + +# Whether this is a CA certificate or not +#ca + +# Whether this certificate will be used for a TLS client +#tls_www_client + +# Whether this certificate will be used for a TLS server +tls_www_server + +# Whether this certificate will be used to sign data (needed +# in TLS DHE ciphersuites). +#signing_key + +# Whether this certificate will be used to encrypt data (needed +# in TLS RSA ciphersuites). Note that it is prefered to use different +# keys for encryption and signing. +encryption_key + +# Whether this key will be used to sign other certificates. +#cert_signing_key + +# Whether this key will be used to sign CRLs. +#crl_signing_key + +# Whether this key will be used to sign code. +#code_signing_key + +# Whether this key will be used to sign OCSP data. +#ocsp_signing_key + +# Whether this key will be used for time stamping. +#time_stamping_key diff --git a/bin/tiny_socket.py b/bin/tiny_socket.py index 746d6c22671..cb51aefc140 100644 --- a/bin/tiny_socket.py +++ b/bin/tiny_socket.py @@ -33,11 +33,13 @@ class Myexception(Exception): class mysocket: def __init__(self, sock=None): if sock is None: - self.sock = socket.socket( - socket.AF_INET, socket.SOCK_STREAM) + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) else: self.sock = sock - self.sock.settimeout(120) + # self.sock.settimeout(120) + # prepare this socket for long operations: it may block for infinite + # time, but should exit as soon as the net is down + self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) def connect(self, host, port=False): if not port: protocol, buf = host.split('//') @@ -61,8 +63,8 @@ class mysocket: buf='' while len(buf) < 8: chunk = self.sock.recv(8 - len(buf)) - if chunk == '': - raise RuntimeError, "socket connection broken" + if not chunk: + raise socket.timeout buf += chunk size = int(buf) buf = self.sock.recv(1) @@ -73,8 +75,8 @@ class mysocket: msg = '' while len(msg) < size: chunk = self.sock.recv(size-len(msg)) - if chunk == '': - raise RuntimeError, "socket connection broken" + if not chunk: + raise socket.timeout msg = msg + chunk msgio = cStringIO.StringIO(msg) unpickler = cPickle.Unpickler(msgio) diff --git a/bin/tools/config.py b/bin/tools/config.py index f9d381a5b72..2dd6b1131bf 100644 --- a/bin/tools/config.py +++ b/bin/tools/config.py @@ -66,6 +66,7 @@ class configmanager(object): 'import_partial': "", 'pidfile': None, 'logfile': None, + 'logrotate': '1', 'smtp_server': 'localhost', 'smtp_user': False, 'smtp_port':25, @@ -81,6 +82,8 @@ class configmanager(object): 'login_message': False, 'list_db' : True, } + + self.misc = {} hasSSL = check_ssl() @@ -131,6 +134,8 @@ class configmanager(object): # Logging Group group = optparse.OptionGroup(parser, "Logging Configuration") 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", + default=None, help="do not rotate the logfile") group.add_option("--syslog", action="store_true", dest="syslog", default=False, help="Send the log to the syslog server") group.add_option('--log-level', dest='log_level', type='choice', choices=self._LOGLEVELS.keys(), @@ -220,20 +225,19 @@ class configmanager(object): keys = ['interface', 'port', 'db_name', 'db_user', 'db_password', 'db_host', 'db_port', 'list_db', 'logfile', 'pidfile', 'smtp_port', 'cache_timeout', 'email_from', 'smtp_server', 'smtp_user', 'smtp_password', 'price_accuracy', - 'netinterface', 'netport', 'db_maxconn', 'import_partial', 'addons_path'] + 'netinterface', 'netport', 'db_maxconn', 'import_partial', 'addons_path', + 'netrpc', 'xmlrpc', 'syslog', 'without_demo'] if hasSSL: keys.extend(['smtp_ssl', 'secure_cert_file', 'secure_pkey_file']) + keys.append('secure') for arg in keys: if getattr(opt, arg): self.options[arg] = getattr(opt, arg) keys = ['language', 'translate_out', 'translate_in', 'debug_mode', - 'stop_after_init', 'without_demo', 'netrpc', 'xmlrpc', 'syslog'] - - if hasSSL and not self.options['secure']: - keys.append('secure') + 'stop_after_init', 'logrotate'] for arg in keys: if getattr(opt, arg) is not None: @@ -337,6 +341,18 @@ class configmanager(object): if value=='False' or value=='false': value = False self.options[name] = value + #parse the other sections, as well + for sec in p.sections(): + if sec == 'options': + continue + if not self.misc.has_key(sec): + self.misc[sec]= {} + for (name, value) in p.items(sec): + if value=='True' or value=='true': + value = True + if value=='False' or value=='false': + value = False + self.misc[sec][name] = value except IOError: pass except ConfigParser.NoSectionError: @@ -353,6 +369,10 @@ class configmanager(object): p.set('options', opt, loglevelnames.get(self.options[opt], self.options[opt])) else: p.set('options', opt, self.options[opt]) + + for sec in self.misc.keys(): + for opt in self.misc[sec].keys(): + p.set(sec,opt,self.misc[sec][opt]) # try to create the directories and write the file try: @@ -371,6 +391,9 @@ class configmanager(object): def get(self, key, default=None): return self.options.get(key, default) + def get_misc(self, sect, key, default=None): + return self.misc.get(sect,{}).get(key, default) + def __setitem__(self, key, value): self.options[key] = value diff --git a/bin/tools/convert.py b/bin/tools/convert.py index 122bbae2b42..bc603c761c0 100644 --- a/bin/tools/convert.py +++ b/bin/tools/convert.py @@ -105,7 +105,12 @@ def _eval_xml(self,node, pool, cr, uid, idref, context=None): all_timezones=[] pytz=pytzclass() idref2['pytz'] = pytz - return eval(a_eval, idref2) + try: + return eval(a_eval, idref2) + except: + logger = netsvc.Logger() + logger.notifyChannel("init", netsvc.LOG_WARNING, 'could eval(%s) for %s in %s, please get back and fix it!' % (a_eval,node.getAttribute('name'),context)) + return "" if t == 'xml': def _process(s, idref): m = re.findall('[^%]%\((.*?)\)[ds]', s) @@ -231,12 +236,12 @@ class xml_import(object): id = xml_id if '.' in xml_id: module, id = xml_id.split('.', 1) - assert '.' not in id, """The ID reference "%s" must contains + assert '.' not in id, """The ID reference "%s" must contain maximum one dot. They are used to refer to other modules ID, in the form: module.record_id""" % (xml_id,) if module != self.module: modcnt = self.pool.get('ir.module.module').search_count(self.cr, self.uid, ['&', ('name', '=', module), ('state', 'in', ['installed'])]) - assert modcnt == 1, """The ID "%s" refer to an uninstalled module""" % (xml_id,) + assert modcnt == 1, """The ID "%s" refers to an uninstalled module""" % (xml_id,) if len(id) > 64: self.logger.notifyChannel('init', netsvc.LOG_ERROR, 'id: %s is to long (max: 64)'% (id,)) @@ -274,8 +279,11 @@ form: module.record_id""" % (xml_id,) res['report_sxw_content'] = sxw_content if rec.get('header'): res['header'] = eval(rec.get('header','')) + if rec.get('report_type'): + res['report_type'] = rec.get('report_type','') res['multi'] = rec.get('multi','') and eval(rec.get('multi','')) - xml_id = rec.get('id','').encode('utf8') + xml_id = rec.get('id','').encode('utf8') + self._test_xml_id(xml_id) if rec.get('groups'): diff --git a/bin/tools/misc.py b/bin/tools/misc.py index d1e4f9a3914..c3394c2ed4f 100644 --- a/bin/tools/misc.py +++ b/bin/tools/misc.py @@ -173,8 +173,8 @@ def file_open(name, mode="r", subdir='addons', pathinfo=False): @return: fileobject if pathinfo is False else (fileobject, filepath) """ - - adp = os.path.normcase(os.path.abspath(config['addons_path'])) + import addons + adps = addons.ad_paths rtp = os.path.normcase(os.path.abspath(config['root_path'])) if name.replace(os.path.sep, '/').startswith('addons/'): @@ -189,7 +189,8 @@ def file_open(name, mode="r", subdir='addons', pathinfo=False): subdir2 = (subdir2 != 'addons' or None) and subdir2 - try: + for adp in adps: + try: if subdir2: fn = os.path.join(adp, subdir2, name) else: @@ -199,7 +200,7 @@ def file_open(name, mode="r", subdir='addons', pathinfo=False): if pathinfo: return fo, fn return fo - except IOError, e: + except IOError, e: pass if subdir: @@ -382,6 +383,18 @@ def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=Non def write(self, s): self.logger.notifyChannel('email_send', netsvc.LOG_DEBUG, s) + smtp_server = config['smtp_server'] + if smtp_server.startswith('maildir:/'): + from mailbox import Maildir + maildir_path = smtp_server[8:] + try: + mdir = Maildir(maildir_path,factory=None, create = True) + mdir.add(msg.as_string(True)) + return True + except Exception,e: + netsvc.Logger().notifyChannel('email_send (maildir)', netsvc.LOG_ERROR, e) + return False + try: oldstderr = smtplib.stderr s = smtplib.SMTP() @@ -391,9 +404,8 @@ def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=Non if debug: smtplib.stderr = WriteToLogger() - s.set_debuglevel(int(bool(debug))) # 0 or 1 - - s.connect(config['smtp_server'], config['smtp_port']) + s.set_debuglevel(int(bool(debug))) # 0 or 1 + s.connect(smtp_server, config['smtp_port']) if ssl: s.ehlo() s.starttls() @@ -737,14 +749,14 @@ def ustr(value): return unicode(value, getlocale()[1]) def exception_to_unicode(e): - if hasattr(e, 'message'): + if (sys.version_info[:2] < (2,6)) and hasattr(e, 'message'): return ustr(e.message) if hasattr(e, 'args'): return "\n".join((ustr(a) for a in e.args)) try: return ustr(e) except: - return u"Unknow message" + return u"Unknown message" # to be compatible with python 2.4 @@ -799,7 +811,7 @@ def get_languages(): 'cs_CZ': u'Czech / Čeština', 'da_DK': u'Danish / Dansk', 'de_DE': u'German / Deutsch', - 'el_EL': u'Greek / Ελληνικά', + 'el_GR': u'Greek / Ελληνικά', 'en_CA': u'English (CA)', 'en_GB': u'English (UK)', 'en_US': u'English (US)', @@ -850,11 +862,11 @@ def get_user_companies(cr, user): def _get_company_children(cr, ids): if not ids: return [] - cr.execute('SELECT id FROM res_company WHERE parent_id = any(array[%s])' %(','.join([str(x) for x in ids]),)) + cr.execute('SELECT id FROM res_company WHERE parent_id = ANY (%s)', (ids,)) res=[x[0] for x in cr.fetchall()] res.extend(_get_company_children(cr, res)) return res - cr.execute('SELECT comp.id FROM res_company AS comp, res_users AS u WHERE u.id = %s AND comp.id = u.company_id' % (user,)) + cr.execute('SELECT comp.id FROM res_company AS comp, res_users AS u WHERE u.id = %s AND comp.id = u.company_id', (user,)) compids=[cr.fetchone()[0]] compids.extend(_get_company_children(cr, compids)) return compids diff --git a/bin/tools/safe_eval.py b/bin/tools/safe_eval.py new file mode 100644 index 00000000000..bfbc0b34fe9 --- /dev/null +++ b/bin/tools/safe_eval.py @@ -0,0 +1,66 @@ +# -*- encoding: utf-8 -*- +# +# Copyright P. Christeas 2008,2009 +# +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +############################################################################### + +__export_bis = {} +import sys + +def __init_ebis(): + global __export_bis + + _evars = [ 'abs', 'all', 'any', 'basestring' , 'bool', + 'chr', 'cmp','complex', 'dict', 'divmod', 'enumerate', + 'float', 'frozenset', 'getattr', 'hasattr', 'hash', + 'hex', 'id','int', 'iter', 'len', 'list', 'long', 'map', 'max', + 'min', 'oct', 'ord','pow', 'range', 'reduce', 'repr', + 'reversed', 'round', 'set', 'setattr', 'slice','sorted', 'str', + 'sum', 'tuple','type', 'unichr','unicode', 'xrange', + 'True','False', 'None', 'NotImplemented', 'Ellipsis', ] + + if sys.version_info[0:2] >= (2,6): + _evars.extend(['bin', 'format', 'next']) + for v in _evars: + __export_bis[v] = __builtins__[v] + + +__init_ebis() + + +def safe_eval(expr,sglobals,slocals = None): + """ A little safer version of eval(). + This one, will use fewer builtin functions, so that only + arithmetic and logic expressions can really work """ + + global __export_bis + + if not sglobals.has_key('__builtins__'): + # we copy, because we wouldn't want successive calls to safe_eval + # to be able to alter the builtins. + sglobals['__builtins__'] = __export_bis.copy() + + return eval(expr,sglobals,slocals) + +#eof diff --git a/bin/tools/translate.py b/bin/tools/translate.py index 8a43fbbe768..57b15002a4e 100644 --- a/bin/tools/translate.py +++ b/bin/tools/translate.py @@ -133,8 +133,11 @@ class GettextAlias(object): return source cr = frame.f_locals.get('cr') - lang = (frame.f_locals.get('context') or {}).get('lang', False) - if not (lang and cr): + try: + lang = (frame.f_locals.get('context') or {}).get('lang', False) + if not (lang and cr): + return source + except: return source cr.execute('select value from ir_translation where lang=%s and type=%s and src=%s', (lang, 'code', source)) @@ -151,6 +154,7 @@ class TinyPoFile(object): def __iter__(self): self.buffer.seek(0) self.lines = self._get_lines() + self.lines_count = len(self.lines); self.first = True self.tnrs= [] @@ -165,6 +169,9 @@ class TinyPoFile(object): lines.append('') # ensure that the file ends with at least an empty line return lines + def cur_line(self): + return (self.lines_count - len(self.lines)) + def next(self): def unquote(str): return str[1:-1].replace("\\n", "\n") \ @@ -178,14 +185,31 @@ class TinyPoFile(object): else: tmp_tnrs = [] line = None + fuzzy = False while (not line): if 0 == len(self.lines): raise StopIteration() line = self.lines.pop(0).strip() if line.startswith('#:'): - tmp_tnrs.append( line[2:].strip().split(':') ) - if line.startswith('#'): - line = None + if ' ' in line[2:].strip(): + for lpart in line[2:].strip().split(' '): + tmp_tnrs.append(lpart.strip().split(':',2)) + else: + tmp_tnrs.append( line[2:].strip().split(':',2) ) + elif line.startswith('#,') and (line[2:].strip() == 'fuzzy'): + fuzzy = True + line = self.lines.pop(0).strip() + while not line: + # allow empty lines between comments and msgid + line = self.lines.pop(0).strip() + if line.startswith('#~ '): + while line.startswith('#~ ') or not line.strip(): + if 0 == len(self.lines): + raise StopIteration() + line = self.lines.pop(0) + # This has been a deprecated entry, don't return anything + return self.next() + if not line.startswith('msgid'): raise Exception("malformed file: bad line: %s" % line) @@ -202,7 +226,7 @@ class TinyPoFile(object): while not line.startswith('msgstr'): if not line: - raise Exception('malformed file') + raise Exception('malformed file at %d'% self.cur_line()) source += unquote(line) line = self.lines.pop(0).strip() @@ -212,12 +236,15 @@ class TinyPoFile(object): trad += unquote(line) line = self.lines.pop(0).strip() - if tmp_tnrs: + if tmp_tnrs and not fuzzy: type, name, res_id = tmp_tnrs.pop(0) for t, n, r in tmp_tnrs: self.tnrs.append((t, n, r, source, trad)) self.first = False + + if name == None: + return self.next() return type, name, res_id, source, trad def write_infos(self, modules): @@ -251,8 +278,9 @@ class TinyPoFile(object): def write(self, modules, tnrs, source, trad): def quote(s): return '"%s"' % s.replace('"','\\"') \ - .replace('\\ ','\\\\ ') \ - .replace('\n', '\\n"\n"') + .replace('\n', '\\n"\n"') \ + .replace(' \\ ',' \\\\ ') + plurial = len(modules) > 1 and 's' or '' self.buffer.write("#. module%s: %s\n" % (plurial, ', '.join(modules))) @@ -404,11 +432,14 @@ def trans_generate(lang, modules, dbname=None): query = 'SELECT name, model, res_id, module' \ ' FROM ir_model_data' - if not 'all' in modules: + query_param = None + if 'all_installed' in modules: + query += ' WHERE module IN ( SELECT name FROM ir_module_module WHERE state = \'installed\') ' + elif not 'all' in modules: query += ' WHERE module IN (%s)' % ','.join(['%s']*len(modules)) + query_param = modules query += ' ORDER BY module, model, name' - query_param = not 'all' in modules and modules or None cr.execute(query, query_param) _to_translate = [] @@ -443,8 +474,8 @@ def trans_generate(lang, modules, dbname=None): push_translation(module, 'view', encode(obj.model), 0, t) elif model=='ir.actions.wizard': service_name = 'wizard.'+encode(obj.wiz_name) - if netsvc.SERVICES.get(service_name): - obj2 = netsvc.SERVICES[service_name] + if netsvc.Service._services.get(service_name): + obj2 = netsvc.Service._services[service_name] for state_name, state_def in obj2.states.iteritems(): if 'result' in state_def: result = state_def['result'] @@ -459,6 +490,9 @@ def trans_generate(lang, modules, dbname=None): } # export fields + if not result.has_key('fields'): + logger.notifyChannel("db",netsvc.LOG_WARNING,"res has no fields: %r" % result) + continue for field_name, field_def in result['fields'].iteritems(): res_name = name + ',' + field_name @@ -483,7 +517,11 @@ def trans_generate(lang, modules, dbname=None): push_translation(module, 'wizard_button', res_name, 0, button_label) elif model=='ir.model.fields': - field_name = encode(obj.name) + try: + field_name = encode(obj.name) + except AttributeError, exc: + logger.notifyChannel("db", netsvc.LOG_ERROR, "name error in %s: %s" % (xml_name,str(exc))) + continue objmodel = pool.get(obj.model) if not objmodel or not field_name in objmodel._columns: continue @@ -540,15 +578,30 @@ def trans_generate(lang, modules, dbname=None): for field_name,field_def in pool.get(model)._columns.items(): if field_def.translate: name = model + "," + field_name - trad = getattr(obj, field_name) or '' + try: + trad = getattr(obj, field_name) or '' + except: + trad = '' push_translation(module, 'model', name, xml_name, encode(trad)) # parse source code for _() calls - def get_module_from_path(path): - relative_addons_path = tools.config['addons_path'][len(tools.config['root_path'])+1:] - if path.startswith(relative_addons_path) and (os.path.dirname(path) != relative_addons_path): - path = path[len(relative_addons_path)+1:] - return path.split(os.path.sep)[0] + def get_module_from_path(path,mod_paths=None): + if not mod_paths: + # First, construct a list of possible paths + def_path = os.path.abspath(os.path.join(tools.config['root_path'], 'addons')) # default addons path (base) + ad_paths= map(lambda m: os.path.abspath(m.strip()),tools.config['addons_path'].split(',')) + mod_paths=[def_path] + for adp in ad_paths: + mod_paths.append(adp) + if not adp.startswith('/'): + mod_paths.append(os.path.join(def_path,adp)) + elif adp.startswith(def_path): + mod_paths.append(adp[len(def_path)+1:]) + + for mp in mod_paths: + if path.startswith(mp) and (os.path.dirname(path) != mp): + path = path[len(mp)+1:] + return path.split(os.path.sep)[0] return 'base' # files that are not in a module are considered as being in 'base' module modobj = pool.get('ir.module.module') @@ -707,6 +760,8 @@ def trans_load_data(db_name, fileobj, fileformat, lang, strict=False, lang_name= # if the resource id (res_id) is in that list, use it, # otherwise use the whole list + if not ids: + ids = [] ids = (dic['res_id'] in ids) and [dic['res_id']] or ids for id in ids: dic['res_id'] = id diff --git a/bin/wizard/__init__.py b/bin/wizard/__init__.py index c5e34ae3fc0..456fa6efb32 100644 --- a/bin/wizard/__init__.py +++ b/bin/wizard/__init__.py @@ -43,7 +43,7 @@ class interface(netsvc.Service): states = {} def __init__(self, name): - assert not netsvc.service_exist('wizard.'+name), 'The wizard "%s" already exists!'%name + assert not self.service_exist('wizard.'+name), 'The wizard "%s" already exists!'%name super(interface, self).__init__('wizard.'+name) self.exportMethod(self.execute) self.wiz_name = name diff --git a/bin/workflow/instance.py b/bin/workflow/instance.py index 204b0f93240..e1407fd1455 100644 --- a/bin/workflow/instance.py +++ b/bin/workflow/instance.py @@ -27,9 +27,8 @@ import pooler def create(cr, ident, wkf_id): (uid,res_type,res_id) = ident - cr.execute("select nextval('wkf_instance_id_seq')") + cr.execute('insert into wkf_instance (res_type,res_id,uid,wkf_id) values (%s,%s,%s,%s) RETURNING id', (res_type,res_id,uid,wkf_id)) id_new = cr.fetchone()[0] - cr.execute('insert into wkf_instance (id,res_type,res_id,uid,wkf_id) values (%s,%s,%s,%s,%s)', (id_new,res_type,res_id,uid,wkf_id)) cr.execute('select * from wkf_activity where flow_start=True and wkf_id=%s', (wkf_id,)) res = cr.dictfetchall() stack = [] diff --git a/change-loglevel.sh b/change-loglevel.sh new file mode 100755 index 00000000000..cb325435a1a --- /dev/null +++ b/change-loglevel.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +ADMIN_PASSWD='admin' +method_1() { + cat '-' << EOF + + + set_loglevel + + $ADMIN_PASSWD + + + $1 + + + +EOF +} +LEVEL=10 + +if [ -n "$1" ] ; then LEVEL=$1 ; fi + +method_1 $LEVEL | POST -c 'text/xml' http://localhost:8069/xmlrpc/common +#eof diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 00000000000..2f1950b39d4 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,1067 @@ +openerp-server (5.0.6-0-1test4) unstable; urgency=low + + [ Christophe Simonis ] + * [IMP] cchange version number + + [ Jay (Open ERP) ] + * [FIX] Send mail : Non-English characters were throwing error. + * [IMP] Base : Rate for ARS Added + * [FIX] Base : 'Supplier Partners' menu will open new record with + supplier=1,customer=0 + * [FIX] Security Loophole corrected + * [FIX] Send Mail : Unicode error handled + * [FIX] Translation issue with cache: it needed to restart + server,SOLVED + + [ Harry (Open ERP) ] + * [IMP]quality_integration_server: put max_limit in server connection + * [IMP]quality_integration_server : quality html log + + [ Jay (Open ERP) ] + * [FIX] Export : Error solved while Exporting O2M records with None + value of any field + + [ Fabien Pinckaers ] + * merge + + [ P. Christeas ] + * Debian changelog for 5.0.4 + * Two hacks to make it python2.5 compatible.. + + [ Fabien Pinckaers ] + * [FIX] better log error, new line + + [ P. Christeas ] + * Implement safe_eval() in order to armour eval() + * [Feature]: add condition to ir.sequence, let it be partitioned. + * safe_eval: make python 2.5 compatible. + * Backport the HTTP server code from python 2.6 to 2.5 + + [ Jay (Open ERP) ] + * [FIX] Better translation exportation. + + [ P. Christeas ] + * ORM feature: allow renaming of columns. + * Web services: fix database operations (create, drop etc.) for new + API. + * ORM: Protect against dropping columns. + + [ Jay (Open ERP) ] + * [IMP] Rng : Accepting Groups under/after/replacing Buttons + + [ Fabien Pinckaers ] + * [FIX] regression in fields.function computation + + [ Harry (Open ERP) ] + * [IMP]quality_integration_server: get message from + base_quality_module if module can not success to reach minimun score + * [FIX]quality_integration_server: get message from + base_quality_module if module can not success to reach minimun score + + [ Mantavya Gajjar ] + * [REM]: remove the name search method, as this method was implemented + for the email address field + * [MERGE]: merging from the same branch + + [ Harry (Open ERP) ] + * [FIX]quality_integration_server : quality log : reduce overwrite + last test log in other test logs + * [FIX]quality_integration_server: fixe problem on make link of extra- + module in addons + + [ tailor ] + * [IMP] updated changelog file + * [MERGE] + * [IMP] updated changelog file + * [IMP] updated release.py to 5.0.5 + + [ Jay (Open ERP) ] + * [FIX] Price Accuracy : rounding made to be based on --price_accuracy + option + * [FIX] Fields.function : Store value computation corrected + + [ P. Christeas ] + * [IMP]: allow binary function fields to directly return size. + * Ir.attachment: improve SQL for access checks. + + [ tailor ] + * [REL] release.py: 5.0.6 + * [IMP] updated changelog file for 5.0.6 release + + [ Harry (Open ERP) ] + * [IMP]quality_integration_server: quality log : add new option to + specify qualitylog path to store html pages of log + + [ Jay (Open ERP) ] + * [FIX] Update Module : Float8 to float and numeric to float casting + made possible + + [ Harry (Open ERP) ] + * [FIX]quality_integration_server: ascci encoding problem in quality + html log + + [ P. Christeas ] + * [Fix] Tolerate ids that came as strings. + + [ Jay (Open ERP) ] + * [FIX] Fields.function : Store value computation corrected + * [FIX] Export Translation : Warning on Non-existing record instead of + breaking flow + + [ mra (Open ERP) ] + * [IMP] base_quality_interrrogation: put message if the score of + quality less then minimal and remove unit test condition + + [ Jay (Open ERP) ] + * [FIX] Allowing sql keywords as fields(don't use them in order by + clause) + + [ Christophe Simonis ] + * [IMP] cron: check the arguments to avoid security issues + + [ P. Christeas ] + * New API: work better for bad ExportServices. + + [ Jay (Open ERP) ] + * [FIX] Name_search() is having now record limit to be 80 instead of + None + * [FIX] Import : Context of the action/screen passed,taken into + consideration while importing + * [FIX] Improved Previous bad commit for pagelayout error + * [FIX] Domain was getting failed when trying to work upon M2M,O2M + field of object + * [FIX] Import made successful when field is O2M and it has relation + under that. + + [ Stephane Wirtel ] + * [MERGE] Backport: http://bazaar.launchpad.net/~openobject- + training/+junk/server/revision/1821 + * [MERGE] Backport: http://bazaar.launchpad.net/~openobject- + training/+junk/server/revision/1822 + + [ HDA (OpenERP) ] + * merged for report improvements + + [ Stephane Wirtel ] + * [FIX] Specify the name of the argument for the context, to avoir a + critical bug + + [ Christophe Simonis ] + * [FIX] allow to call write without ids on osv_memory objects + + [ Fabien Pinckaers ] + * [FIX] default context + + [ Stephane Wirtel ] + * [FIX] Use an alternative for the locale.RADIXCHAR if this one + doesn't exist + + [ P. Christeas ] + * [Imp] Allow expressions: ".. child_of, 1" , not only list(1) + + [ Jay (Open ERP) ] + * [FIX] Improved Previous bad commit for pagelayout error + + [ Fabien Pinckaers ] + * [IMP] default value for module + * [FIX] recursive child_of on one2many and many2many + * [FIX] recursive calls + + [ Jay (Open ERP) ] + * [FIX] Export : Selection field gets imported by its external name + if export is not import compatible + * [FIX] Expression : domain calculation failes, max. recursion error + protected + + [ P. Christeas ] + * Mandriva: when server starts/stops, run scripts + + [ Jay (Open ERP) ] + * [FIX] Config : wrong calculation if cached_timeout comes from config + file + + [ P. Christeas ] + * [IMP] If SSL connection fails, close the socket. + + [ Jay (Open ERP) ] + * [FIX] Ir_attachment : Context updation corrected on preview + * [FIX] Record rule : domain evaluation problem corrected + + [ P. Christeas ] + * [IMP] Convert: two minor typos and tab expansion. + * Netsvc.Server: Expand tabs + * Server: get_stats() RPC call for any extended info. + * Client script for statistics. + * Net-RPC: armor the thread, provide stats and inf. timeout. + + [ Jay (Open ERP) ] + * [REF] Partner : copy() improved + + [ panos ] + + -- panos Mon, 02 Nov 2009 18:28:44 -0500 + +openerp-server (5.0.4-0-1test3) intrepid; urgency=high + + [ Panos Christeas ] + * Testing release, with HTTP/1.1 features + + [ P. Christeas ] + * Translating: skip buggy wizard states. + * If the server is only translating, don't init the httpd. + * Remove debuggint print's + * Implement basic-authenticated services. Log messages. + * Initialize services before databases. + * Don't print messages, log them.. + * Bring back net-rpc service. + * Fix quoting of ids when passed to sql. + * Fix merging of 5.0.4 into API-changed netsvc. + + [ Jay (Open ERP) ] + * [FIX] Security Loophole corrected + + [ P. Christeas ] + * Fix https transports, certificates etc. + * [IMP] reorder the imports in websrv_lib. + * Fix dispatching of reports after API change. + * [MAJOR IMP] Rewrite the http/RPC engine and let HTTP/1.1 features. + + [ Christophe Simonis ] + * [REL] 5.0.4 + * [MERGE] + * [MERGE] + * [IMP] replace partner name and address in demo + * [IMP] add translation names + * [IMP] update translations files + + [ Jay (Open ERP) ] + * [FIX] Functional fields with M2O relation can be printed on print + screen now + + [ HDA (OpenERP) ] + * [FIX] Pagecount probelm while all objects print on same page and + printing special character on xsl report + + [ Harry (Open ERP) ] + * [FIX] import: support ":db_id" , better warning message + + [ Jay (Open ERP) ] + * [FIX] ir_cron :function and its arguements are now editable + + [ HDA (OpenERP) ] + * [Merge] + + [ Panos Christeas ] + + -- Panos Christeas Tue, 08 Sep 2009 10:06:51 +0300 + +openerp-server (5.0.3-0-1ubuntu2) intrepid; urgency=low + + * Merge packaging work from Debian official + + -- Panos Christeas Mon, 24 Aug 2009 22:10:00 +0300 + +openerp-server (5.0.3-0-1ubuntu1) intrepid; urgency=low + + [ P. Christeas ] + * Introduce 'debug2' loglevel, use it for SQL + + [ Christophe Simonis ] + * [IMP] change version number + + [ P. Christeas ] + * [IMP]: allow Log level to be changed in runtime. + * Config: misc options. Logger: option to turn env_info off. + * Remove base_setup/i18n/el_GR.po, module is moved to addons repo. + * Merge el_GR translation with template. + * Update translation template. + * Fix +x permission of openerp-server.py + + -- Panos Christeas Fri, 14 Aug 2009 20:40:29 +0300 + +openerp-server (5.0.3-0-1) unstable; urgency=low + + * Merging upstream version 5.0.3-0. + * Removing xmlrpc.patch, went upstream. + * Using dedicated storage directory in /var/lib/openerp-server, that + way the addons directory can stay read-only for the unprivileged + user. + * Commenting out db_name in config (Closes: #542391). + * Commenting out port in config (Closes: #542406). + * Renaming logfile to openerp-server.log for consistency. + * Commenting out pidfile in config (Closes: #542427). + * Removing debconf handling in postrm, not possible to do that. + * Removing local storage directory on purge. + + -- Daniel Baumann Mon, 24 Aug 2009 20:16:55 +0200 + +openerp-server (5.0.2-0-3) unstable; urgency=low + + * Wrapping and sorting depends. + * Correcting spelling of Open ERP. + * Updating maintainer field. + * Updating vcs fields. + * Updating to standards version 3.8.3. + * Dropping old depends on python-xml (Closes: #543127). + * Adding maintainer homepage field to control. + * Marking maintainer homepage field to be also included in binary + packages and changelog. + + -- Daniel Baumann Mon, 24 Aug 2009 18:23:54 +0200 + +openerp-server (5.0.2-0-2) unstable; urgency=high + + * Adding patch from Panos Christeas to forbid RPC + calls without credentials. All versions of openerp-server affected. + + -- Daniel Baumann Thu, 13 Aug 2009 14:45:17 +0200 + +openerp-server (5.0.2-0-1ubuntu2) intrepid; urgency=critical + + [ P. Christeas ] + * Security: check_creds is not really needed, use "check" + + [ Christophe Simonis ] + * [FIX] security issue: avoid access to inactive users + * [FIX] avoid a bug when look in stack when translate code strings + + [ P. Christeas ] + * [SEC] [CRITICAL] Forbid RPC calls w/o credentials. + + [ Panos Christeas ] + + -- Panos Christeas Thu, 13 Aug 2009 15:25:26 +0300 + +openerp-server (5.0.2-0-1ubuntu1) intrepid; urgency=low + + [ P. Christeas ] + * [b98f26433c82] Greek translations, shorten terms. + * [2567b4e8fbc4] [IMP] Partial restrict operator for domains. + * [a507f9bee128] Shortcuts in RNG view definition. + * [e8da054afe0a] [IMP] Allow OS to rotate the logs, use + WatchedFileHandler if needed. + + [ Jay (Open ERP) ] + * [8174407a30ec] [FIX] Property : Removed old referenced values from + property + + [ mra (Open ERP) ] + * [819f9df328fa] [REV] revert chanegs for revision no 1810 + * [70535423260f] [FIX] loading module sequence while creating new + database + + [ Fabien Pinckaers ] + * [5aa83e5bb359] [FIX] bug 369947 + * [dcde99cd9c5e] [FIX] bug 371496 + * [8fbad91c586e] [FIX] bug 371768 bad tooltip + * [f42a9ae9115b] merge + * [db313489098d] merge + + [ dsh (Open ERP) ] + * [a86dc3304dde] [IMP] attributes of barcode + + [ Jay (Open ERP) ] + * [b2dafdb68cce] [FIX] ir_translation : corrected entry insertion when + adding translation + + [ husen daudi ] + * [696445ddfb83] [MERGE] + * [7f9d0d2dd239] [FIX] module:base made name field translatable in + action.server (ref:mga) + + [ Jay (Open ERP) ] + * [ed97326339a8] [FIX] Character Truncation problem on updation solved + + [ Christophe Simonis ] + * [0b6c92ede314] [IMP] ir_model_data: convert assert to sql constraint + + exception + + [ Jay (Open ERP) ] + * [f98cc5a0bee6] [FIX] Custom report : sorted X-axis values + * [0cf0628f3c8a] [FIX] SQL Constraint failure error on particular + record while importing + + [ husen daudi ] + * [d56bb59ca02d] [FIX] xml encoding in header for &.<.> + * [04d765f45692] [Merge] Converted xml reports from xmldom to etree + and better error message with environment information + + [ Jay (Open ERP) ] + * [a915dc15dbfd] [FIX] User with blank password(if so) allowed to log + in. + + [ Fabien Pinckaers ] + * [19e2f2b84eac] [FIX] avoid required parameters rule (domain_force) + * [b8d383a666e6] merge + * [7cdc6778acb1] merge + * [70c8fcdf67f2] merge + * [7cd04889bd27] [IMP] better error message + + [ Jay (Open ERP) ] + * [99f3b0db73f8] [FIX] Fixed oldxml at python2.6 for Ubuntu + 8.0.4,9.04 + * [14b488955652] [IMP] default_xx in context isuue + + [ Fabien Pinckaers ] + * [15a617ecbe04] merge + * [26f02d120db1] [fix] launch signal after action in workflows + + [ Jay (Open ERP) ] + * [0ff7ddcd394d] [FIX] ORM: Defult_field name in context belongs to + curent object only, not to its relationals anymore + * [843604436871] [FIX] ir_attachment preview method corrected + + [ Stephane Wirtel ] + * [406601019f23] [MERGE] + * [fa17b9e5f302] [FIX] fields.related (m2o poiting to M2M/O2M should + be skipped during write. + + [ Jay (Open ERP) ] + * [7feb86ed7ad4] [IMP] Temporarily commented exception for browse + record that broke reports from wizard + + [ husen daudi ] + * [665433813e7f] [FIX] Bad commit + * [c866dcb38c74] [FIX] Added PageCount tag in report engine + + [ Fabien Pinckaers ] + * [a79d7777c888] merge + * [60d6357d3b2b] modifs + * [9ed525cfcd51] [FIX] bug in report engine + + [ Jay (Open ERP) ] + * [2a571396d23e] [FIX] GIF image is previewed + + [ Christophe Simonis ] + * [cee0846dff3c] [MERGE] + * [d2a31d33e1b1] [FIX] upgrade doesn't try to delete modules + * [1376272db30b] [MERGE] + * [bbaad3809f36] [FIX] xml: the tag delete also the reference + to the deleted objects into ir_model_data + * [35d4aa682b36] [FIX] translation export: catch the case when a + object does not exists in database + * [1f0cb7576704] [FIX] browse_record: raise a better exception when + the id doesn't exists + + [ dsh (Open ERP) ] + * [0c8fe65fe3c0] [FIX] fix &,<,> problem for html2html-url report + * [71298ebb40b4] [FIX] fix &,<,> problem for html2html report + * [58b9464b7813] [IMP] added style for the url which are as text + + [ Fabien Pinckaers ] + * [b934dd73d408] merge + * [fe33fdc8df19] [FIX] translations resynchro terms + * [4efe96b4f584] [FIX] Bugfix translations and float/int format in + reports + * [688883ce8183] bugfix_lang_notavailabel + + [ Olivier Laurent ] + * [bee98151efd0] [MERGE] + * [adf826c7285b] [IMP] base: french translations + + [ Christophe Simonis ] + * [cf01b2f3ee31] [IMP] maintenance: complete the remarks with the + database name + * [34e10ece0768] [FIX] complete rng file + * [eb179c00941a] [FIX] load the module graph even if it containt only + "base" + + [ dsh (Open ERP) ] + * [5f1426c9e322] bug fix(skip text if child nodes are there) for the + html2html and openoffice reports + + [ Panos Christeas ] + + [ husen daudi ] + * Revert bad commit + + [ Christophe Simonis ] + * [REL] 5.0.2 + * [MERGE] + * [FIX] ensure sys is imported + + [ Jay (Open ERP) ] + * [FIX] _inherits was misbehaving on search + + [ Christophe Simonis ] + * [FIX] pass the context to check method of 'ir.model.access' + * [FIX] disallow the deletion of records set as default properties + * [MERGE] + * [FIX] increase size of field "name" of ir.model.data + * [IMP] update french translations + * [IMP] update french translations + + [ Jay (Open ERP) ] + * [REF] Unused argument removed + + [ Christophe Simonis ] + * [IMP] update po(t) files + + [ Jay (Open ERP) ] + * [FIX] Login : Login and password accepting accented characters + * [FIX] Error reporting : library error corrected + * [FIX] default_xxx in context was passed to relational + fields,corrected. + + [ P. Christeas ] + * Fix whitespace merging, that broke logging. + + [ Jay (Open ERP) ] + * [FIX] Print screen now displays the report as per WYSIWYG + * [FIX]Importation problem for bool fields corrected + + [ Christophe Simonis ] + * [IMP] new method that ollow the server to return a message that will + be display on login page + + [ Jay (Open ERP) ] + * [FIX] ir_model_data : SQL_Constraint failure covered + * [FIX] Accented characters are displayed on footer now + * [FIX] DB operations(backup,restore) process corrected,were blocked + if db_port was supplied + + [ husen daudi ] + * Merged + + [ Jay (Open ERP) ] + * [FIX] Expression calculation for Datetime Corrected + * [FIX] Logger notification improved + * [FIX] ir_model : context key deletion + * [FIX] Translation : Text with prefix '_' from py files included for + exports. + * [FIX] Corrected bug of expression calculation in previous commit + + [ Olivier Laurent ] + * [FIX] sql expression: in search, add the time part to datetime field + when it's not present + + [ P. Christeas ] + * email_send: feature to place mails in a maildir, instead of smtp + * Fix Greek translation of email "subject". + + [ Olivier Laurent ] + * [FIX] setup.py: py2exe now adds 'zoneinfo' directory in library.zip + + [ Jay (Open ERP) ] + * [FIX] Importation problem corrected for unicode. + * [IMP] Environment Information Notification Improved + + [ husen daudi ] + * [FIX] place tag + + [ Olivier Laurent ] + * [MERGE] + * [FIX] tools.amount_to_text_en: now displays cents + + [ P. Christeas ] + * Greek translations, shorten terms. + + [ Olivier Laurent ] + * [MERGE] + * [FIX] RuntimeError in the cache system + + [ Jay (Open ERP) ] + * [FIX] Print screen reports will behave acc. to locale for + date,datetime + * [FIX] Import : context provided for language translation + + [ dsh (Open ERP) ] + * [FIX] size miss match for page and frame + + [ husen daudi ] + * [FIX] removed report etree warning of len + * [FIX] Added sql_constraint on adding field having size<1 + + [ Quentin De Paoli ] + * [FIX] security on wokflows bugfixed. The security has to be checked + using the current uid, not the one that created the workflow + instance! + + [ Harry (Open ERP) ] + * [FIX] export window : use context for import compatable option in + pass argument instend of added new argemenent + * [IMP] import/export : added Database ID, ID + + [ P. Christeas ] + * [IMP] Partial restrict operator for domains. + * Shortcuts in RNG view definition. + * [IMP] Allow OS to rotate the logs, use WatchedFileHandler if needed. + + [ Olivier Laurent ] + * [MERGE] + * [FIX] supply password problem with pg_dump, pg_restore on win32 + * [IMP] french translations + + [ Jay (Open ERP) ] + * [FIX] Certificate in non-digit format will not be accepted + * [FIX] Certificate in non-digit format will not be accepted + + [ husen daudi ] + * [IMP] Improved environment information message + + [ Jay (Open ERP) ] + * [Fix] CSV Import : Accepting 'Value' of selection field + from[(key,value)] + * [FIX] Property : Removed old referenced values from property + + [ mra (Open ERP) ] + * [REV] revert chanegs for revision no 1810 + * [FIX] loading module sequence while creating new database + + [ Fabien Pinckaers ] + * [FIX] bug 369947 + * [FIX] bug 371496 + * [FIX] bug 371768 bad tooltip + * merge + * merge + + [ dsh (Open ERP) ] + * [IMP] attributes of barcode + + [ Jay (Open ERP) ] + * [FIX] ir_translation : corrected entry insertion when adding + translation + + [ husen daudi ] + * [MERGE] + * [FIX] module:base made name field translatable in action.server + (ref:mga) + + [ Jay (Open ERP) ] + * [FIX] Character Truncation problem on updation solved + + [ Christophe Simonis ] + * [IMP] ir_model_data: convert assert to sql constraint + exception + + [ Jay (Open ERP) ] + * [FIX] Custom report : sorted X-axis values + * [FIX] SQL Constraint failure error on particular record while + importing + + [ husen daudi ] + * [FIX] xml encoding in header for &.<.> + * [Merge] Converted xml reports from xmldom to etree and better error + message with environment information + + [ Jay (Open ERP) ] + * [FIX] User with blank password(if so) allowed to log in. + + [ Fabien Pinckaers ] + * [FIX] avoid required parameters rule (domain_force) + * merge + * merge + * merge + * [IMP] better error message + + [ Jay (Open ERP) ] + * [FIX] Fixed oldxml at python2.6 for Ubuntu 8.0.4,9.04 + * [IMP] default_xx in context isuue + + [ Fabien Pinckaers ] + * merge + * [fix] launch signal after action in workflows + + [ Jay (Open ERP) ] + * [FIX] ORM: Defult_field name in context belongs to curent object + only, not to its relationals anymore + * [FIX] ir_attachment preview method corrected + + [ Stephane Wirtel ] + * [MERGE] + * [FIX] fields.related (m2o poiting to M2M/O2M should be skipped + during write. + + [ Jay (Open ERP) ] + * [IMP] Temporarily commented exception for browse record that broke + reports from wizard + + [ husen daudi ] + * [FIX] Bad commit + * [FIX] Added PageCount tag in report engine + + [ Fabien Pinckaers ] + * merge + * modifs + * [FIX] bug in report engine + + [ Jay (Open ERP) ] + * [FIX] GIF image is previewed + + [ Christophe Simonis ] + * [MERGE] + * [FIX] upgrade doesn't try to delete modules + * [MERGE] + * [FIX] xml: the tag delete also the reference to the deleted + objects into ir_model_data + * [FIX] translation export: catch the case when a object does not + exists in database + * [FIX] browse_record: raise a better exception when the id doesn't + exists + + [ dsh (Open ERP) ] + * [FIX] fix &,<,> problem for html2html-url report + * [FIX] fix &,<,> problem for html2html report + * [IMP] added style for the url which are as text + + [ Fabien Pinckaers ] + * merge + * [FIX] translations resynchro terms + * [FIX] Bugfix translations and float/int format in reports + * bugfix_lang_notavailabel + + [ Olivier Laurent ] + * [MERGE] + * [IMP] base: french translations + + [ Christophe Simonis ] + * [IMP] maintenance: complete the remarks with the database name + * [FIX] complete rng file + * [FIX] load the module graph even if it containt only "base" + + [ dsh (Open ERP) ] + * bug fix(skip text if child nodes are there) for the html2html and + openoffice reports + + [ Panos Christeas ] + + -- Panos Christeas Thu, 13 Aug 2009 10:21:28 +0300 + +openerp-server (5.0.2-0-1) unstable; urgency=low + + * Updating standards to 3.8.1. + * Rediffing autobuild.patch (Closes: #538625). + * Upgrading package to standards version 3.8.2. + * Managing setup of unprivileged user account with debconf. + * Using more common directory name to store local debian additions. + * Updating README.Debian to reflect that the database has to be + initialized through the client (Closes: #518675). + * Removing package leftovers in postrm script. + * Merging upstream version 5.0.2-0. + + -- Daniel Baumann Thu, 13 Aug 2009 11:24:59 +0200 + +openerp-server (5.0.1-0-1ubuntu2) karmic; urgency=low + + * Minor updates + + -- Panagiotis Kranidiotis Thu, 18 Jun 2009 19:05:32 +0300 + +openerp-server (5.0.1-0-1ubuntu1) karmic; urgency=low + + * New greek translations added + + -- Panagiotis Kranidiotis Wed, 17 Jun 2009 18:45:37 +0300 + +openerp-server (5.0.1-0-1) unstable; urgency=low + + * Merging upstream version 5.0.1-0. + * Correcting path of openerp-server in README.Debian (Closes: + #520890). + * Correcting user handling in init script and config file (Closes: + #513263, #516348). + * Setting port to 8070. + * Also mentioning debug_mode and price_accuracy in config file + (Closes: #513264). + * Using correct rfc-2822 date formats in changelog. + * Rediffing shebang.patch. + + -- Daniel Baumann Sat, 30 May 2009 12:53:39 +0200 + +openerp-server (5.0.0-3-1) unstable; urgency=low + + * Merging upstream version 5.0.0-3. + * Improving init call in README.Debian, thanks to David Goodenough + . + * Fixed wrapping in README.Debian. + + -- Daniel Baumann Sat, 14 Feb 2009 00:51:00 +0100 + +openerp-server (5.0.0-2-1) unstable; urgency=low + + * Merging upstream version 5.0.0-2 (Closes: #514920). + * Updating README.Debian. + + -- Daniel Baumann Sat, 14 Feb 2009 00:12:00 +0100 + +openerp-server (5.0.0-1) unstable; urgency=low + + * Merging upstream version 5.0.0. + + -- Daniel Baumann Sat, 07 Feb 2009 13:33:00 +0100 + +openerp-server (5.0.0~rc3-1) unstable; urgency=low + + * Adding note about initializing the database in README.Debian. + * Adding changelog for debian version 4.2.3.4-3. + * Merging upstream version 5.0.0~rc3. + * Using quilt rather than dpatch. + * Updating year in copyright file. + * Updating python-openssl depends. + * Updating lintian overrides. + + -- Daniel Baumann Fri, 09 Jan 2009 18:31:00 -0500 + +openerp-server (5.0.0~rc2-1) unstable; urgency=low + + * Updating python xml depends (Closes: #508911). + * Merging upstream version 5.0.0~rc2. + * New upstream no longer uses embedded copies of pydot, pychart and + reportlab (Closes: #468104). + * Rediffing shebang.dpatch. + + -- Daniel Baumann Thu, 25 Dec 2008 15:13:00 +0100 + +openerp-server (5.0.0~rc1.1-2) unstable; urgency=low + + * Adjusting sed call to correct path in /usr/bin/openerp-server. + + -- Daniel Baumann Wed, 17 Dec 2008 08:32:00 +0100 + +openerp-server (5.0.0~rc1.1-1) unstable; urgency=low + + * Merging upstream version 5.0.0~rc1.1. + + -- Daniel Baumann Tue, 16 Dec 2008 13:08:00 +0100 + +openerp-server (5.0.0~rc1-1) unstable; urgency=low + + * Merging upstream version 5.0.0~rc1. + * Removing openerp.dpatch, went upstream. + * Rediffing shebang.dpatch. + * Removing workaround for import_xml.rng, not needed anymore. + + -- Daniel Baumann Tue, 16 Dec 2008 12:51:00 +0100 + +openerp-server (5.0.0~alpha-3) unstable; urgency=low + + * Adding ghostscript, python-matplotlib, and python-pyopenssl to recommends. + * Correcting chown calls in postinst. + * Prefixing debhelper files with package name. + * Adding changelog for debian version 4.2.3.4-2. + * Dropping tinyerp-server transitional package, this allows to have both + packages available in unstable. + + -- Daniel Baumann Sun, 07 Dec 2008 20:13:00 +0100 + +openerp-server (5.0.0~alpha-2) experimental; urgency=low + + * Renaming tinyerp-server to new upstream openerp-server name. + + -- Daniel Baumann Sun, 09 Nov 2008 18:59:00 +0100 + +tinyerp-server (5.0.0~alpha-1) experimental; urgency=low + + * Merging upstream version 5.0.0~alpha. + * Rediffing autobuild.dpatch. + * Removing shebang.dpatch, not needed anymore. + * Removing python2.5.dpatch, not needed anymore. + * Rediffing openerp.dpatch. + * Rediffing migrate.dpatch. + * Not moving server to sbin anymore for the sake of consistency. + * Removing unneeded chmod call for tinyerp-server.py. + * Sorting build-depends, depends and recommends. + * Dropping /etc/default/tinyerp-server in favour of using + /etc/tinyerp-server.conf directly. + * Updating chmod call in rules to also cope with filenames that embedd + whitespaces. + * Adding patch to correct shebang in two addon files. + * Adding workaround for bug in setup.py that puts import_xml.rng into the + wrong location. + * Adding symlink for tinyerp_serverrc manpage to tinyerp-server.conf. + * Renaming everything except the package name itself from tinyerp-server to + openerp-server. + * Updating copyright file to current upstream. + + -- Daniel Baumann Sun, 09 Nov 2008 15:52:00 +0100 + +tinyerp-server (4.2.3.4-3) unstable; urgency=high + + * Updating python depends (Closes: #506615). + * Adding note about initializising the database in README.Debian + (Closes: #464557). + + -- Daniel Baumann Mon, 10 Nov 2008 12:40:00 +0100 + +tinyerp-server (4.2.3.4-2) unstable; urgency=low + + * Correcting chown calls in postinst. + + -- Daniel Baumann Mon, 10 Nov 2008 12:40:00 +0100 + +tinyerp-server (4.2.3.4-1) unstable; urgency=low + + * Merging upstream version 4.2.3.4. + * Upgrading package to debhelper 7. + * Upgrading package to standards 3.8.0. + * Updating homepage field in control file. + * Adding vcs fields in control file. + * Rewriting copyright file in machine-interpretable format. + * Using lintian debhelper to install lintian overrides. + * Removing bind-exit.dpatch, went upstream. + * Updating default database port. + * Adding logfile handling. + * Updating postresql recommends. + * Reordering and splitting out rules file into individual debhelper files. + * Applying some shell cosmetics to init and maintainer scripts. + * Adding patch to update homepage location of tinyerp. + * Setting ownership of addons directory in postinst (Closes: #487112). + * Adding patch from Brian DeRocher to fix sql syntax in + migrate script (Closes: #467517). + + -- Daniel Baumann Sun, 09 Nov 2008 09:11:00 +0100 + +tinyerp-server (4.2.2-2) unstable; urgency=medium + + * Readding depends to python-psycopg (Closes: #463079, #493374). + * Adding depends to python-tz (Closes: #482359). + + -- Daniel Baumann Sun, 03 Aug 2008 00:20:00 +0200 + +tinyerp-server (4.2.2-1) unstable; urgency=low + + * New upstream release (Closes: #477698). + * Dropping depends against python-xml (Closes: #468619). + + -- Daniel Baumann Sat, 26 Apr 2008 16:15:00 +0200 + +tinyerp-server (4.2.1-1) unstable; urgency=low + + * Maintainer upload from the Zuerich BSP. + * New upstream release. + * Bumping to new policy. + * Using new homepage field in control. + * Including documentation for migration and testing (Closes: #445464). + * Adjusting 04-bind-exit.dpatch to new upstream release. + * Added lintian overrides. + * Depending now on python-psycopg2, not python-psycopg anymore + (Closes: #445464). + + -- Daniel Baumann Sat, 12 Jan 2008 15:20:00 +0100 + +tinyerp-server (4.2.0-1) unstable; urgency=medium + + * New upstream release. + + -- Daniel Baumann Wed, 31 Oct 2007 21:31:00 +0100 + +tinyerp-server (4.0.3-3) unstable; urgency=medium + + * Setting database port to 5433 (Closes: #443626). + * Applied patch from Aldrin Martoq to make tinyerp-server compatible with + python 2.5. + * Applied patch from Luca Falavigna to fix exception + raised when address is already in use. + + -- Daniel Baumann Sat, 29 Sep 2007 17:07:00 +0200 + +tinyerp-server (4.0.3-2) unstable; urgency=low + + * Check for existence of deluser in postrm (Closes: #431532). + + -- Daniel Baumann Tue, 03 Jul 2007 11:01:00 +0200 + +tinyerp-server (4.0.3-1) unstable; urgency=low + + * New upstream release. + * Taking package back, Jean-Marc seems to be MIA. + * Changed wording of 'listen to all interfaces' paragraph in README.Debian, + thanks to Gerfried Fuchs . + * Added lsb header to init script. + + -- Daniel Baumann Fri, 01 Jun 2007 11:59:00 +0200 + +tinyerp-server (4.0.2-3) unstable; urgency=low + + * Setting maintainer to Jean-Marc, this time really :) + + -- Daniel Baumann Wed, 28 Mar 2007 21:48:00 +0100 + +tinyerp-server (4.0.2-2) unstable; urgency=low + + * Setting maintainer to Jean-Marc. + + -- Daniel Baumann Wed, 07 Feb 2007 13:41:00 +0100 + +tinyerp-server (4.0.2-1) unstable; urgency=low + + * New upstream release. + * Some minor cleanups. + + -- Daniel Baumann Thu, 18 Jan 2007 14:19:00 +0100 + +tinyerp-server (4.0.1-1) unstable; urgency=low + + * New upstream release. + * Removed 03-setup.dpatch, went upstream. + + -- Daniel Baumann Fri, 29 Dec 2006 01:03:00 +0100 + +tinyerp-server (4.0.0-1) unstable; urgency=low + + * New upstream release. + * Added patch to fix a typo in setup.py. + + -- Daniel Baumann Tue, 05 Dec 2006 17:43:00 +0100 + +tinyerp-server (4.0.0~rc1-2) unstable; urgency=low + + * Cleaned up build-depends. + + -- Daniel Baumann Tue, 05 Dec 2006 13:19:00 +0100 + +tinyerp-server (4.0.0~rc1-1) unstable; urgency=low + + * New upstream release. + + -- Daniel Baumann Tue, 05 Dec 2006 12:57:00 +0100 + +tinyerp-server (3.5.0-1) experimental; urgency=low + + * New upstream release. + + -- Daniel Baumann Mon, 23 Oct 2006 12:23:00 +0200 + +tinyerp-server (3.4.2-1) unstable; urgency=low + + * New upstream release. + * New email address. + * Complying with new python policy (Closes: #380973). + * Adjusted postgre depends (Closes: #376614). + + -- Daniel Baumann Mon, 16 Oct 2006 14:45:00 +0200 + +tinyerp-server (3.3.0-1) unstable; urgency=low + + * New upstream release (Closes: #369769): + - fixed installation script to install all needed files (Closes: #355224) + * Updated README.Debian (Closes: #352322, #360222, #360223). + * Set to architecture to all (Closes: #356962). + + -- Daniel Baumann Sun, 04 Jun 2006 00:50:00 +0100 + +tinyerp-server (3.2.1-1) unstable; urgency=low + + * New upstream release. + + -- Daniel Baumann Thu, 02 Feb 2006 09:44:00 +0100 + +tinyerp-server (3.2.0-1) unstable; urgency=low + + * New upstream release. + * Adjusted shellbang in bin/addons/base/ir/workflow/pydot/dot_parser.py. + + -- Daniel Baumann Tue, 24 Jan 2006 07:00:00 +0100 + +tinyerp-server (3.1.99+3.2.0rc1-1) unstable; urgency=low + + * New upstream release. + + -- Daniel Baumann Tue, 27 Dec 2005 20:00:00 +0100 + +tinyerp-server (3.1.1+debian-1) unstable; urgency=low + + * Initial release (Closes: #301510). + * Rebuild orig.tar.gz to remove unnecessary files in upstreams debian/. + * Added changelog from website. + + -- Daniel Baumann Sun, 16 Oct 2005 13:35:00 +0200 diff --git a/debian/compat b/debian/compat new file mode 100644 index 00000000000..7f8f011eb73 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +7 diff --git a/debian/control b/debian/control new file mode 100644 index 00000000000..880a0761c04 --- /dev/null +++ b/debian/control @@ -0,0 +1,35 @@ +Source: openerp-server +Section: net +Priority: optional +Maintainer: Jimmy Angelakos +Uploaders: Daniel Baumann +Build-Depends: debhelper (>= 7), po-debconf, python-dev, quilt +Build-Depends-Indep: python-libxslt1, python-lxml, python-psycopg2 +Standards-Version: 3.8.3 +Homepage: http://www.openerp.com/ +Vcs-Browser: http://git.debian-maintainers.org/?p=open-object/openerp-server.git +Vcs-Git: git://git.debian-maintainers.org/git/open-object/openerp-server.git +XSBC-Maintainer-Homepage: http://open-object.debian-maintainers.org/ + +Package: openerp-server +Section: net +Architecture: all +Depends: + ${misc:Depends}, adduser, python, python-libxslt1, python-lxml, + python-psycopg2, python-pydot, python-pychart, python-reportlab, python-tz +Conflicts: tinyerp-server +Replaces: tinyerp-server +Recommends: + graphviz, ghostscript, postgresql, postgresql-client, python-imaging, + python-matplotlib, python-openssl, python-pyparsing +Suggests: openerp-client +Description: Enterprise Resource Management (server) + Open ERP, previously known as TinyERP, is a complete ERP and CRM. The main + features are accounting (analytic and financial), stock management, sales and + purchases management, tasks automation, marketing campaigns, help desk, POS, + etc. Technical features include a distributed server, flexible workflows, an + object database, a dynamic GUI, customizable reports, and NET-RPC and XML-RPC + interfaces. + . + This package contains the Open ERP server, install openerp-client package for + the client. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 00000000000..6a75021214c --- /dev/null +++ b/debian/copyright @@ -0,0 +1,245 @@ +Author: Tiny.be +Download: http://www.openerp.com/ + +Files: * +Copyright: (C) 2004-2009 Tiny.be +License: GPL-3+ + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + . + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program. If not, see . + . + On Debian systems, the complete text of the GNU General Public License + can be found in /usr/share/common-licenses/GPL-3 file. + +Files: + bin/addons/gen_graph.sh + doc/migrate/* +Copyright: (C) 2004-2008 Tiny.be +License: GPL-2+ + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + . + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + . + On Debian systems, the complete text of the GNU General Public License + can be found in /usr/share/common-licenses/GPL-2 file. + +Files: + bin/addons/account/report/general_ledger.py + bin/addons/account/report/general_ledger_landscape.py + bin/addons/account/wizard/wizard_statement_from_invoice.py +Copyright: (C) 2005-2008 CamptoCamp +License: GPL-2+ + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + . + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + . + On Debian systems, the complete text of the GNU General Public License + can be found in /usr/share/common-licenses/GPL-2 file. + +Files: bin/addons/auction/barcode/* +Copyright: (C) 2000 Tyler C. Sarna +License: BSD + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + . + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. All advertising materials mentioning features or use of this software + must display the following acknowledgement: + This product includes software developed by Tyler C. Sarna. + 4. Neither the name of the author nor the names of contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + . + THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +Files: + bin/addons/base_report_designer/wizard/tiny_sxw2rml/normalized_oo2rml.xsl + bin/addons/base_report_designer/wizard/tiny_sxw2rml/tiny_sxw2rml.py +Copyright: (C) 2005 Martin Simon +License: LGPL-2.1 + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + . + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + . + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + . + On Debian systems, the complete text of the GNU Library General Public License + can be found in /usr/share/common-licenses/LGPL-2.1 file. + +Files: bin/addons/document/ftpserver/* +Copyright: + (C) 2007 Giampaolo Rodola + (C) 2008 Fabien Pinckaers +License: MIT + Permission to use, copy, modify, and distribute this software and + its documentation for any purpose and without fee is hereby + granted, provided that the above copyright notice appear in all + copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + Giampaolo Rodola' not be used in advertising or publicity pertaining to + distribution of the software without specific, written prior + permission. + . + Giampaolo Rodola' DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN + NO EVENT Giampaolo Rodola' BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +Files: bin/addons/hr_holidays/* +Copyright: + (C) 2004-2008 Tiny.be + (C) 2005-2006 Axelor SARL. +License: GPL-2+ + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + . + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + . + On Debian systems, the complete text of the GNU General Public License + can be found in /usr/share/common-licenses/GPL-2 file. + +Files: bin/addons/l10n_chart_uk_minimal/* +Copyright: + (C) 2004-2008 Tiny.be + (C) 2004-2008 Seath Solutions Ltd. +License: GPL-2+ + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + . + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + . + On Debian systems, the complete text of the GNU General Public License + can be found in /usr/share/common-licenses/GPL-2 file. + +Files: bin/tools/threadinglocal.py +Copyright: (C) 2004-2005 CherryPy Team +License: BSD + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + . + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the CherryPy Team nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + . + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Files: bin/tools/decimal.py +Copyright: (C) 2004 Python Software Foundation. +License: other + This file is distributed under the Python Software License + (http://www.python.org/2.3/license.html). + . + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose and without fee is hereby granted, + provided that the above copyright notice appear in all copies and that + both that copyright notice and this permission notice appear in + supporting documentation, and that the name of Stichting Mathematisch + Centrum or CWI not be used in advertising or publicity pertaining to + distribution of the software without specific, written prior + permission. + +Files: debian/* +Copyright: (C) 2005-2009 Daniel Baumann +License: GPL-3+ + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + . + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program. If not, see . + . + On Debian systems, the complete text of the GNU General Public License + can be found in /usr/share/common-licenses/GPL-3 file. diff --git a/debian/local/openerp-server.conf b/debian/local/openerp-server.conf new file mode 100644 index 00000000000..ffb6ed49db3 --- /dev/null +++ b/debian/local/openerp-server.conf @@ -0,0 +1,55 @@ +# /etc/openerp-server.conf(5) - configuration file for openerp-server(1) + +[options] +# Enable the debugging mode (default False). +verbose = False +debug_mode = False + +# The file where the server pid will be stored (default False). +#pidfile = /var/run/openerp.pid + +# The file where the server log will be stored (default False). +logfile = /var/log/openerp-server.log + +# The unix account on behalf openerp is running. +process_user = openerp + +# The IP address on which the server will bind. +# If empty, it will bind on all interfaces (default empty). +interface = localhost + +# The TCP port on which the server will listen (default 8069). +#port = 8070 + +# Enable debug mode (default False). +debug_mode = False + +# Launch server over https instead of http (default False). +secure = False + +# Specify the SMTP server for sending email (default localhost). +smtp_server = localhost + +# Specify the SMTP user for sending email (default False). +smtp_user = False + +# Specify the SMTP password for sending email (default False). +smtp_password = False + +# Specify the database name. +#db_name = openerp + +# Specify the database user name (default None). +db_user = openerp + +# Specify the database password for db_user (default None). +db_password = + +# Specify the database host (default localhost). +db_host = + +# Specify the database port (default None). +db_port = 5432 + +# Specify the price accuracy. +#price_accuracy = diff --git a/debian/openerp-server.README.Debian b/debian/openerp-server.README.Debian new file mode 100644 index 00000000000..39dede87780 --- /dev/null +++ b/debian/openerp-server.README.Debian @@ -0,0 +1,49 @@ +openerp-server for Debian +------------------------- + +Open ERP uses a PostgreSQL database to store its data. With the first generation +of packages, you have to setup this database manually. Here is a short +explanation how to achieve this (you need to execute all commands as root): + + 0. Making sure, PostgreSQL is running + + # /etc/init.d/postgresql restart + + Note that depending on the version of PostgreSQL installed on your system, + the above syvinit script could also be named postgresql-VERSION (whereas + 'VERSION' needs to be replace with a version number). + + 1. Creating the database user + + # su - postgres -c "createuser -q --createdb --no-createrole --pwprompt openerp" + + Note: If you want to run the database as another user than 'openerp', you + need to replace 'openerp' above with the user you want instead, and you + need to adjust 'db_user = openerp' in /etc/openerp-server.conf too. + + 3. Restarting openerp-server + + # /etc/init.d/openerp-server restart + + + 4. Initializing the database + + Now you can connect with Open ERP client to the database and initialize it. + +Now, you're finish. Please be aware of the following things: + + * openerp-server has by default two accounts: + - User: admin; password: admin + - User: demo; password; demo + + * openerp-server listens by default on port 8070. If you need to change this, + edit /etc/openerp-server.conf and replace 'port = 8070' with + 'port = '. + + * openerp-server in the upstreams configuration listens by default to *all* + interfaces. For security reasons, we do restrict it in the Debian packages + to listen only on localhost. If you need to change this, edit + /etc/openerp-server.conf and replace 'interface = localhost' with + 'interface = '. + + -- Daniel Baumann Fri, 1 Jun 2007 12:00:00 +0200 diff --git a/debian/openerp-server.config b/debian/openerp-server.config new file mode 100644 index 00000000000..cf081ab3861 --- /dev/null +++ b/debian/openerp-server.config @@ -0,0 +1,10 @@ +#!/bin/sh + +set -e + +. /usr/share/debconf/confmodule + +db_input low openerp-server/username || true +db_go + +db_stop diff --git a/debian/openerp-server.docs b/debian/openerp-server.docs new file mode 100644 index 00000000000..d5ad552bfce --- /dev/null +++ b/debian/openerp-server.docs @@ -0,0 +1,2 @@ +doc/migrate +doc/tests diff --git a/debian/openerp-server.examples b/debian/openerp-server.examples new file mode 100644 index 00000000000..c7f075ec127 --- /dev/null +++ b/debian/openerp-server.examples @@ -0,0 +1 @@ +debian/openerp-server.preseed diff --git a/debian/openerp-server.init b/debian/openerp-server.init new file mode 100644 index 00000000000..e981d2cffe8 --- /dev/null +++ b/debian/openerp-server.init @@ -0,0 +1,68 @@ +#!/bin/sh + +### BEGIN INIT INFO +# Provides: openerp-server +# Required-Start: $syslog +# Required-Stop: $syslog +# Should-Start: $network +# Should-Stop: $network +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Enterprise Resource Management software +# Description: Open ERP is a complete ERP and CRM software. +### END INIT INFO + +PATH=/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=/usr/bin/openerp-server +NAME=openerp-server +DESC=openerp-server + +USER=openerp + +test -x ${DAEMON} || exit 0 + +set -e + +case "${1}" in + start) + echo -n "Starting ${DESC}: " + + start-stop-daemon --start --quiet --pidfile /var/run/${NAME}.pid \ + --chuid ${USER} --background --make-pidfile \ + --exec ${DAEMON} -- --config=/etc/openerp-server.conf + + echo "${NAME}." + ;; + + stop) + echo -n "Stopping ${DESC}: " + + start-stop-daemon --stop --quiet --pidfile /var/run/${NAME}.pid \ + --oknodo + + echo "${NAME}." + ;; + + restart|force-reload) + echo -n "Restarting ${DESC}: " + + start-stop-daemon --stop --quiet --pidfile /var/run/${NAME}.pid \ + --oknodo + + sleep 1 + + start-stop-daemon --start --quiet --pidfile /var/run/${NAME}.pid \ + --chuid ${USER} --background --make-pidfile \ + --exec ${DAEMON} -- --config=/etc/openerp-server.conf + + echo "${NAME}." + ;; + + *) + N=/etc/init.d/${NAME} + echo "Usage: ${NAME} {start|stop|restart|force-reload}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/debian/openerp-server.install b/debian/openerp-server.install new file mode 100644 index 00000000000..ccfbbb27fd6 --- /dev/null +++ b/debian/openerp-server.install @@ -0,0 +1 @@ +debian/local/openerp-server.conf /etc diff --git a/debian/openerp-server.links b/debian/openerp-server.links new file mode 100644 index 00000000000..67620d22397 --- /dev/null +++ b/debian/openerp-server.links @@ -0,0 +1,2 @@ +/usr/share/man/man5/openerp_serverrc.5.gz /usr/share/man/man5/openerp-server.conf.5.gz +/var/lib/openerp-server/filestore /usr/lib/openerp-server/filestore diff --git a/debian/openerp-server.lintian-overrides b/debian/openerp-server.lintian-overrides new file mode 100644 index 00000000000..25b1165c4be --- /dev/null +++ b/debian/openerp-server.lintian-overrides @@ -0,0 +1,7 @@ +# Add-on directories needs data directories, and sometimes they are +# (intentionally) empty, hence overriting the lintian warning. +openerp-server: package-contains-empty-directory +# Add-on directoires contain images sometimes, but it's nothing that is +# worthwile to be splittet out to /usr/share as it's all manual work for each +# release. +openerp-server: image-file-in-usr-lib diff --git a/debian/openerp-server.postinst b/debian/openerp-server.postinst new file mode 100644 index 00000000000..a1e5317b8fc --- /dev/null +++ b/debian/openerp-server.postinst @@ -0,0 +1,62 @@ +#!/bin/sh + +set -e + +. /usr/share/debconf/confmodule + +CONFFILE="/etc/openerp-server.conf" +LOGFILE="/var/log/openerp-server.log" + +case "${1}" in + configure) + db_version 2.0 + + db_get openerp-server/username + _USERNAME="${RET:-openerp}" + + db_stop + + if ! getent passwd | grep -q "^${_USERNAME}" + then + adduser --system --no-create-home --quiet --gecos 'Open ERP server' --group ${_USERNAME} + else + echo "Open ERP user (${_USERNAME}) already exists, doing nothing." + fi + + sed -i -e "s|^process_user.*$|process_user = ${_USERNAME}|" ${CONFFILE} + + # Creating log file + touch ${LOGFILE} + chown ${_USERNAME}:adm ${LOGFILE} + chmod 0640 ${LOGFILE} + + # Creating local storage directory + mkdir -p /var/lib/openerp-server/filestore + + # Setting ownership and permissions + chmod 0640 ${CONFFILE} + chown ${_USERNAME}:${_USERNAME} ${CONFFILE} + chown ${_USERNAME}:${_USERNAME} /var/lib/openerp-server -R + + echo + echo "* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *" + echo "* Open ERP uses a PostgreSQL database to store its data. With the first *" + echo "* generation of packages, you have to setup this database manually. *" + echo "* Please read /usr/share/doc/openerp-server/README.Debian how to do it. *" + echo "* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *" + echo + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + + ;; + + *) + echo "postinst called with unknown argument \`{$1}'" >&2 + exit 1 + ;; +esac + +#DEBHELPER# + +exit 0 diff --git a/debian/openerp-server.postrm b/debian/openerp-server.postrm new file mode 100644 index 00000000000..02b3d0a6817 --- /dev/null +++ b/debian/openerp-server.postrm @@ -0,0 +1,41 @@ +#!/bin/sh + +set -e + +case "${1}" in + remove) + _USERNAME="openerp" + _GROUPNAME="openerp" + + if [ -x /usr/sbin/deluser ] + then + deluser --quiet --system ${_USERNAME} + fi + + if [ -x /usr/sbin/delgroup ] + then + delgroup --quiet --system --only-if-empty ${_GROUPNAME} || true + fi + + rm -f /usr/lib/openerp-server/addons/__init__.pyc + rmdir --ignore-fail-on-non-empty /usr/lib/openerp-server/addons || true + rmdir --ignore-fail-on-non-empty /usr/lib/openerp-server || true + ;; + + purge) + rm -rf /var/lib/openerp-server + ;; + + upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + + ;; + + *) + echo "postrm called with unknown argument \`${1}'" >&2 + exit 1 + ;; +esac + +#DEBHELPER# + +exit 0 diff --git a/debian/openerp-server.preseed b/debian/openerp-server.preseed new file mode 100644 index 00000000000..66d1a4ab95d --- /dev/null +++ b/debian/openerp-server.preseed @@ -0,0 +1,5 @@ +################################################################################ +## openerp-server + +#openerp-server openerp-server/username string openerp +################################################################################ diff --git a/debian/openerp-server.templates b/debian/openerp-server.templates new file mode 100644 index 00000000000..985bd24abd9 --- /dev/null +++ b/debian/openerp-server.templates @@ -0,0 +1,9 @@ +Template: openerp-server/username +Type: string +Default: openerp +_Description: Dedicated system account for the Open ERP server: + The Open ERP server must use a dedicated account for its operation so that + the system's security is not compromised by running it with superuser + privileges. + . + Please choose that account's username. diff --git a/debian/patches/01-autobuild.patch b/debian/patches/01-autobuild.patch new file mode 100644 index 00000000000..abdd197247b --- /dev/null +++ b/debian/patches/01-autobuild.patch @@ -0,0 +1,15 @@ +Author: Daniel Baumann +Description: Disable modules check to make it buildable without X11. + +diff -Naurp openerp-server.orig/setup.py openerp-server/setup.py +--- openerp-server.orig/setup.py 2009-06-08 15:51:20.000000000 +0000 ++++ openerp-server/setup.py 2009-07-26 10:27:49.000000000 +0000 +@@ -121,7 +121,7 @@ def data_files(): + + return files + +-check_modules() ++#check_modules() + + f = file('openerp-server','w') + start_script = """#!/bin/sh\necho "OpenERP Setup - The content of this file is generated at the install stage\n" """ diff --git a/debian/patches/02-migrate.patch b/debian/patches/02-migrate.patch new file mode 100644 index 00000000000..3004042cb5b --- /dev/null +++ b/debian/patches/02-migrate.patch @@ -0,0 +1,15 @@ +Author: Brian DeRocher +Description: Correct SQL syntax in migrate script (Closes: #467517). + +diff -Naurp openerp-server.orig/doc/migrate/3.4.0-4.0.0/pre.py openerp-server/doc/migrate/3.4.0-4.0.0/pre.py +--- openerp-server.orig/doc/migrate/3.4.0-4.0.0/pre.py 2008-11-03 21:33:56.000000000 +0000 ++++ openerp-server/doc/migrate/3.4.0-4.0.0/pre.py 2008-11-09 09:09:49.000000000 +0000 +@@ -123,7 +123,7 @@ cr.commit() + + for line in ( + "ALTER TABLE ir_module_module ADD demo BOOLEAN", +- "ALTER TABLE ir_module_module SET demo DEFAULT False", ++ "ALTER TABLE ir_module_module alter column demo set DEFAULT False", + "DELETE FROM ir_values WHERE VALUE LIKE '%,False'", + """UPDATE ir_ui_view set arch='' where name='ir.ui.menu.tree' and type='tree' and field_parent='child_id'""", + ): diff --git a/debian/patches/03-shebang.patch b/debian/patches/03-shebang.patch new file mode 100644 index 00000000000..68818e1f733 --- /dev/null +++ b/debian/patches/03-shebang.patch @@ -0,0 +1,12 @@ +Author: Daniel Baumann +Description: Correcting shebang. + +diff -Naurp openerp-server.orig/bin/addons/document/ftpserver/ftpserver.py openerp-server/bin/addons/document/ftpserver/ftpserver.py +--- openerp-server.orig/bin/addons/document/ftpserver/ftpserver.py 2009-05-30 10:14:37.000000000 +0000 ++++ openerp-server/bin/addons/document/ftpserver/ftpserver.py 2009-05-30 11:02:00.000000000 +0000 +@@ -1,4 +1,4 @@ +-#!/usr/bin/env python ++#!/usr/bin/python + # -*- encoding: utf-8 -*- + # ftpserver.py + # diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 00000000000..e69de29bb2d diff --git a/debian/po/POTFILES.in b/debian/po/POTFILES.in new file mode 100644 index 00000000000..eb66b9be4c0 --- /dev/null +++ b/debian/po/POTFILES.in @@ -0,0 +1 @@ +[type: gettext/rfc822deb] openerp-server.templates diff --git a/debian/po/cs.po b/debian/po/cs.po new file mode 100644 index 00000000000..02c16082449 --- /dev/null +++ b/debian/po/cs.po @@ -0,0 +1,39 @@ +# Czech translation of openerp-server debconf templates. +# Copyright (C) 2009 Vítězslav Kotrla +# This file is distributed under the same license as the openerp-server package. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: openerp-server@packages.debian.org\n" +"POT-Creation-Date: 2009-05-30 08:24+0000\n" +"PO-Revision-Date: 2009-06-21 08:20+0200\n" +"Last-Translator: Vítězslav Kotrla \n" +"Language-Team: Czech \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.11.4\n" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "Dedicated system account for the Open ERP server:" +msgstr "Vyhrazený systémový účet, pod kterým bude běžet Open ERP server:" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "" +"The Open ERP server must use a dedicated account for its operation so that the " +"system's security is not compromised by running it with superuser privileges." +msgstr "" +"Pokud by byl Open ERP server spuštěn se superuživatelskými oprávněními, mohlo by dojít " +"ke kompromitaci zabezpečení systému. Proto musí Open ERP server pro svoji činnost používat " +"vyhrazený neprivilegovaný účet." + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "Please choose that account's username." +msgstr "Zvolte prosím název totoho účtu." diff --git a/debian/po/de.po b/debian/po/de.po new file mode 100644 index 00000000000..35f4fa6799f --- /dev/null +++ b/debian/po/de.po @@ -0,0 +1,40 @@ +# German translation of openerp-server debconf templates. +# Copyright (C) 2009 Kai Wasserbäch +# Copyright (C) 2009 Helge Kreutzmann +# This file is distributed under the same license as the openerp-server package. +# +msgid "" +msgstr "" +"Project-Id-Version: openerp-server 5.0.1-0-2\n" +"Report-Msgid-Bugs-To: openerp-server@packages.debian.org\n" +"POT-Creation-Date: 2009-08-24 22:41+0300\n" +"PO-Revision-Date: 2009-04-06 19:01+0200\n" +"Last-Translator: Helge Kreutzmann \n" +"Language-Team: German \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "Dedicated system account for the Open ERP server:" +msgstr "Eigenes Systemkonto für den Open ERP-Server:" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "" +"The Open ERP server must use a dedicated account for its operation so that " +"the system's security is not compromised by running it with superuser " +"privileges." +msgstr "" +"Der Open ERP-Server muss ein eigenes Konto für den Betrieb verwenden, um die " +"Sicherheit des Systems nicht durch das Betreiben mit Superuser-Rechten zu " +"kompromittieren." + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "Please choose that account's username." +msgstr "Bitte wählen Sie den Benutzernamen dieses Kontos." diff --git a/debian/po/es.po b/debian/po/es.po new file mode 100644 index 00000000000..0c32f02fe00 --- /dev/null +++ b/debian/po/es.po @@ -0,0 +1,41 @@ +# Spanish translation of openerp-server debconf templates. +# Copyright (C) 2009 Software in the Public Interest +# 2009 Fernando González de Requena +# This file is distributed under the same license as the openerp-server package. +# +msgid "" +msgstr "" +"Project-Id-Version: openerp-server 5.0.1-0-2\n" +"Report-Msgid-Bugs-To: openerp-server@packages.debian.org\n" +"POT-Creation-Date: 2009-08-24 22:41+0300\n" +"PO-Revision-Date: 2009-03-30 22:35+0200\n" +"Last-Translator: Fernando González de Requena \n" +"Language-Team: Spanish \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.11.4\n" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "Dedicated system account for the Open ERP server:" +msgstr "Cuenta del sistema dedicada para el servidor Open ERP:" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "" +"The Open ERP server must use a dedicated account for its operation so that " +"the system's security is not compromised by running it with superuser " +"privileges." +msgstr "" +"El servidor de Open ERP debe utilizar una cuenta dedicada para su " +"funcionamiento, de tal modo que la seguridad del sistema no se vea " +"comprometida por su utilización con privilegios de administración." + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "Please choose that account's username." +msgstr "Elija un nombre de usuario para esa cuenta." diff --git a/debian/po/fi.po b/debian/po/fi.po new file mode 100644 index 00000000000..716bbdf4cfb --- /dev/null +++ b/debian/po/fi.po @@ -0,0 +1,41 @@ +# Finnish translation of openerp-server debconf templates. +# Copyright (C) 2009 Esko Arajärvi +# This file is distributed under the same license as the openerp-server package. +# +msgid "" +msgstr "" +"Project-Id-Version: openerp-server\n" +"Report-Msgid-Bugs-To: openerp-server@packages.debian.org\n" +"POT-Creation-Date: 2009-08-24 22:41+0300\n" +"PO-Revision-Date: 2009-04-07 22:19+0300\n" +"Last-Translator: Esko Arajärvi \n" +"Language-Team: Finnish \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Lokalize 0.3\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "Dedicated system account for the Open ERP server:" +msgstr "Dedikoitu järjestelmätunnus Open ERP-taustaohjelman ajamiseen:" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "" +"The Open ERP server must use a dedicated account for its operation so that " +"the system's security is not compromised by running it with superuser " +"privileges." +msgstr "" +"Open ERP-palvelimen tulee käyttää dedikoitua tunnusta toiminnoissaan, jotta " +"järjestelmän turvallisuus ei vaarannu kuten käytettäessä " +"pääkäyttäjäoikeuksia." + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "Please choose that account's username." +msgstr "Valitse kyseisen tunnuksen nimi." diff --git a/debian/po/fr.po b/debian/po/fr.po new file mode 100644 index 00000000000..d640bcbcd36 --- /dev/null +++ b/debian/po/fr.po @@ -0,0 +1,39 @@ +# French translation of openerp-server debconf templates. +# Copyright (C) 2009 Steve Petruzzello +# This file is distributed under the same license as the openerp-server package. +# +msgid "" +msgstr "" +"Project-Id-Version: 5.0.1-0-2\n" +"Report-Msgid-Bugs-To: openerp-server@packages.debian.org\n" +"POT-Creation-Date: 2009-08-24 22:41+0300\n" +"PO-Revision-Date: 2009-03-26 01:12+0100\n" +"Last-Translator: Steve Petruzzello \n" +"Language-Team: French \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "Dedicated system account for the Open ERP server:" +msgstr "Identifiant dédié pour le serveur Open ERP:" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "" +"The Open ERP server must use a dedicated account for its operation so that " +"the system's security is not compromised by running it with superuser " +"privileges." +msgstr "" +"Le serveur Open ERP doit être exécuté avec un identifiant spécifique, " +"différent du superutilisateur, afin de ne pas compromettre la sécurité du " +"système." + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "Please choose that account's username." +msgstr "Veuillez choisir cet identifiant." diff --git a/debian/po/gl.po b/debian/po/gl.po new file mode 100644 index 00000000000..cefff9eeb83 --- /dev/null +++ b/debian/po/gl.po @@ -0,0 +1,41 @@ +# Galizian translation of openerp-server debconf templates. +# Copyright (C) 2009 Marce Villarino +# This file is distributed under the same license as the openerp-server package. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: openerp-server@packages.debian.org\n" +"POT-Creation-Date: 2009-08-24 22:41+0300\n" +"PO-Revision-Date: 2009-04-15 21:32+0200\n" +"Last-Translator: marce villarino \n" +"Language-Team: Galician \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Lokalize 0.2\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "Dedicated system account for the Open ERP server:" +msgstr "Conta de usuario do sistema adicada ao servidor Open ERP:" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "" +"The Open ERP server must use a dedicated account for its operation so that " +"the system's security is not compromised by running it with superuser " +"privileges." +msgstr "" +"O servidor Open ERP debe empregar unha conta adicada a el para que a " +"seguridade do sistema non se poña en perigo ao executalo con privilexios de " +"administrador." + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "Please choose that account's username." +msgstr "Escolla o nome de usuario desa conta." diff --git a/debian/po/it.po b/debian/po/it.po new file mode 100644 index 00000000000..3e96cbfa260 --- /dev/null +++ b/debian/po/it.po @@ -0,0 +1,40 @@ +# Italian translation of openerp-server debconf templates. +# Copyright (C) 2009 Vincenzo Campanella +# This file is distributed under the same license as the openerp-server package. +# +msgid "" +msgstr "" +"Project-Id-Version: openerp-server 5.0.1-0-2\n" +"Report-Msgid-Bugs-To: openerp-server@packages.debian.org\n" +"POT-Creation-Date: 2009-08-24 22:41+0300\n" +"PO-Revision-Date: 2009-03-26 08:52+0100\n" +"Last-Translator: Vincenzo Campanella \n" +"Language-Team: Italian \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.11.4\n" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "Dedicated system account for the Open ERP server:" +msgstr "Account di sistema dedicato per il server di Open ERP:" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "" +"The Open ERP server must use a dedicated account for its operation so that " +"the system's security is not compromised by running it with superuser " +"privileges." +msgstr "" +"Il server Open ERP deve utilizzare un account dedicato per eseguire le " +"proprie operazioni, in modo che la sicurezza del sistema non rischi di " +"essere compromessa eseguendolo con privilegi di superutente." + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "Please choose that account's username." +msgstr "Scegliere il nome utente di tale account." diff --git a/debian/po/ja.po b/debian/po/ja.po new file mode 100644 index 00000000000..5af93fb23c6 --- /dev/null +++ b/debian/po/ja.po @@ -0,0 +1,38 @@ +# Japanese translation of openerp-server debconf templates. +# Copyright (C) 2009 Hideki Yamane +# This file is distributed under the same license as the openerp-server package. +# +msgid "" +msgstr "" +"Project-Id-Version: openerp-server 5.0.1-0-2\n" +"Report-Msgid-Bugs-To: openerp-server@packages.debian.org\n" +"POT-Creation-Date: 2009-08-24 22:41+0300\n" +"PO-Revision-Date: 2009-04-09 19:20+0900\n" +"Last-Translator: Hideki Yamane (Debian-JP) \n" +"Language-Team: Japanese \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "Dedicated system account for the Open ERP server:" +msgstr "Open ERP デーモン専用のシステムアカウント:" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "" +"The Open ERP server must use a dedicated account for its operation so that " +"the system's security is not compromised by running it with superuser " +"privileges." +msgstr "" +"Open ERP サーバはその動作について専用のアカウントを使うようになっているため、" +"管理者特権で動作していてもシステムのセキュリティは侵害されません。" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "Please choose that account's username." +msgstr "アカウントのユーザ名を選んでください。" diff --git a/debian/po/pt.po b/debian/po/pt.po new file mode 100644 index 00000000000..530c031877f --- /dev/null +++ b/debian/po/pt.po @@ -0,0 +1,40 @@ +# Portuguese translation of openerp-server debconf templates. +# Copyright (C) 2009 Américo Monteiro +# This file is distributed under the same license as the openerp-server package. +# +msgid "" +msgstr "" +"Project-Id-Version: openerp-server 5.0.1-0-2\n" +"Report-Msgid-Bugs-To: openerp-server@packages.debian.org\n" +"POT-Creation-Date: 2009-08-24 22:41+0300\n" +"PO-Revision-Date: 2009-03-26 19:12+0000\n" +"Last-Translator: Américo Monteiro \n" +"Language-Team: Portuguese \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.11.4\n" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "Dedicated system account for the Open ERP server:" +msgstr "Conta dedicada do sistema para o servidor Open ERP:" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "" +"The Open ERP server must use a dedicated account for its operation so that " +"the system's security is not compromised by running it with superuser " +"privileges." +msgstr "" +"O servidor Open ERP tem que usar uma conta dedicada para as suas operações, " +"isto para que a segurança do sistema não seja comprometida ao corrê-lo com " +"privilégios de superutilizador." + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "Please choose that account's username." +msgstr "Por favor escolha o nome dessa conta." diff --git a/debian/po/ru.po b/debian/po/ru.po new file mode 100644 index 00000000000..ce3dabc9695 --- /dev/null +++ b/debian/po/ru.po @@ -0,0 +1,43 @@ +# German translation of openerp-server debconf templates. +# Copyright (C) 2009 Sergey Alyoshin +# Copyright (C) 2009 Yuri Kozlov +# This file is distributed under the same license as the openerp-server package. +# +msgid "" +msgstr "" +"Project-Id-Version: openerp-server 5.0.1-0-2\n" +"Report-Msgid-Bugs-To: openerp-server@packages.debian.org\n" +"POT-Creation-Date: 2009-08-24 22:41+0300\n" +"PO-Revision-Date: 2009-04-08 21:08+0400\n" +"Last-Translator: Yuri Kozlov \n" +"Language-Team: Russian \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.11.4\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%" +"10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "Dedicated system account for the Open ERP server:" +msgstr "Специально выделенная системная учётная запись для Open ERP службы:" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "" +"The Open ERP server must use a dedicated account for its operation so that " +"the system's security is not compromised by running it with superuser " +"privileges." +msgstr "" +"Для улучшения безопасности системы Open ERP сервер должен использовать " +"специально выделенную учётную запись, а не запускаться с правами " +"суперпользователя." + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "Please choose that account's username." +msgstr "Укажите имя такой учётной записи." diff --git a/debian/po/sv.po b/debian/po/sv.po new file mode 100644 index 00000000000..7c1148081e9 --- /dev/null +++ b/debian/po/sv.po @@ -0,0 +1,40 @@ +# Swedish translation of openerp-server debconf templates. +# Copyright (C) 2009 Martin Bagge +# This file is distributed under the same license as the openerp-server package. +# +msgid "" +msgstr "" +"Project-Id-Version: openerp-server\n" +"Report-Msgid-Bugs-To: openerp-server@packages.debian.org\n" +"POT-Creation-Date: 2009-08-24 22:41+0300\n" +"PO-Revision-Date: 2009-04-07 18:36+0100\n" +"Last-Translator: Martin Bagge \n" +"Language-Team: swedish \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.11.4\n" +"X-Poedit-Language: Swedish\n" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "Dedicated system account for the Open ERP server:" +msgstr "Dedikerat systemkonto för Open ERP server:" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "" +"The Open ERP server must use a dedicated account for its operation so that " +"the system's security is not compromised by running it with superuser " +"privileges." +msgstr "" +"Open ERP servern måste ha ett dedikerat konto för att fungera på ett säkert " +"sätt, att använda privilegier från en superanvändare är inte säkert." + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "Please choose that account's username." +msgstr "Ange kontots användarnamn." diff --git a/debian/po/templates.pot b/debian/po/templates.pot new file mode 100644 index 00000000000..556c76375b0 --- /dev/null +++ b/debian/po/templates.pot @@ -0,0 +1,38 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: openerp-server@packages.debian.org\n" +"POT-Creation-Date: 2009-08-24 22:41+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "Dedicated system account for the Open ERP server:" +msgstr "" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "" +"The Open ERP server must use a dedicated account for its operation so that " +"the system's security is not compromised by running it with superuser " +"privileges." +msgstr "" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "Please choose that account's username." +msgstr "" diff --git a/debian/po/zh_CN.po b/debian/po/zh_CN.po new file mode 100644 index 00000000000..d4c2cc5bf1e --- /dev/null +++ b/debian/po/zh_CN.po @@ -0,0 +1,38 @@ +# Simplified Chinese translation of openerp-server debconf templates. +# Copyright (C) 2009 Deng Xiyue +# This file is distributed under the same license as the openerp-server package. +# +msgid "" +msgstr "" +"Project-Id-Version: openerp-server\n" +"Report-Msgid-Bugs-To: openerp-server@packages.debian.org\n" +"POT-Creation-Date: 2009-08-24 22:41+0300\n" +"PO-Revision-Date: 2009-03-26 16:48+0800\n" +"Last-Translator: Deng Xiyue \n" +"Language-Team: Debian Chinese GB \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "Dedicated system account for the Open ERP server:" +msgstr "指定操作 Open ERP 守护进程的专用系统帐户:" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "" +"The Open ERP server must use a dedicated account for its operation so that " +"the system's security is not compromised by running it with superuser " +"privileges." +msgstr "" +"Open ERP 服务器必须使用一个专用的账户来进行操作,这样就不会因为使用超级用户权" +"限运行而破坏系统的安全。" + +#. Type: string +#. Description +#: ../openerp-server.templates:1001 +msgid "Please choose that account's username." +msgstr "请选择账户的用户名。" diff --git a/debian/rules b/debian/rules new file mode 100755 index 00000000000..1e598f4d580 --- /dev/null +++ b/debian/rules @@ -0,0 +1,80 @@ +#!/usr/bin/make -f + +SHELL := sh -e + + +update: + # Needs: shell-helper + + cd debian; \ + debconf-create-preseed *.config; \ + + for FILE in debian/*.preseed; \ + do \ + grep -v preseed $$FILE > $$FILE.tmp; \ + mv $$FILE.tmp $$FILE; \ + echo $$FILE >> debian/`basename $$FILE .preseed`.examples; \ + done + +clean: clean-patched unpatch +clean-patched: patch + dh_testdir + dh_testroot + rm -f build-stamp + + NO_CHECK_MODULES=1 python setup.py clean + rm -rf build openerp-server + -find $(CURDIR) -type f -name "*.pyc" | xargs rm -f + + debconf-updatepo + + dh_clean + +build: + +install: patch + dh_testdir + dh_testroot + dh_prep + dh_installdirs + + NO_CHECK_MODULES=1 python setup.py install --no-compile --prefix=$(CURDIR)/debian/openerp-server/usr + + # Adjusting program location + sed -i -e 's|cd .*python.*/site-packages|cd /usr/lib|' debian/openerp-server/usr/bin/openerp-server + mv debian/openerp-server/usr/lib/python*/site-packages/openerp-server debian/openerp-server/usr/lib + rm -rf debian/openerp-server/usr/lib/python* + + # Fixing permissions + find debian/openerp-server/usr/lib/openerp-server/addons -type f -print0 | xargs -0 chmod 0644 + + # Removing double files + rm -rf debian/openerp-server/usr/share/doc/openerp-server-* + +binary: binary-indep + +binary-arch: + +binary-indep: install + dh_testdir + dh_testroot + dh_installchangelogs doc/Changelog + dh_installdocs + dh_installexamples + dh_install + dh_installinit --update-rcd-params='defaults 21' + dh_installdebconf + dh_lintian + dh_link + dh_compress + dh_fixperms + dh_installdeb + dh_gencontrol + dh_md5sums + dh_builddeb + +patch: + +unpatch: + +.PHONY: clean build install binary binary-arch binary-indep patch unpatch diff --git a/doc/Changelog-4.x.moved b/doc/Changelog-4.x.moved new file mode 100644 index 00000000000..d884fe8d391 --- /dev/null +++ b/doc/Changelog-4.x.moved @@ -0,0 +1,951 @@ +4.2.1 + Bugfixes + Fix context for source_count function + Create stock move on production for products without BOM lines + Add IBAN fields in bank view + Fix uninitialize variable in import data + Update due date on invoice when payment term change + Fix store on field function that have type many2one or one2one + Request summary must be truncate + Partner event name must be truncate + Remove parent field on partner contact view + Fix icon type on journal period + Remove exception on the size of char field + Fix reference on move line that comes from invoice (Customer != Supplier) + Add function search on sheet_id of timesheet_sheet + Don't return 0 for balance account if there is no fiscal year + Fix set to draft for expense, now really set to draft + Add product and partner in the recursive call of tax compute + Don't compute balance account for inactive account + Fix bad encoding in log message on report_sxw + Fix overdue report for refund lines + Don't start server in non secure mode if secure mode have been set + Fix default value of move line if move_id is not find + Fix _product_partner_ref for cannot concatenate 'str' and 'bool' objects + Add partner_id in the context of SO for browsing the product + Fix multi-tax code on invoice + Fix tax definition for Belgium chart + Remove compute debit/credit on inactive account + Fix the way the tax are rounded for invoice with tax included prices + Fix SO to use the right uom and price to create invoice + Fix on_chnage uos on SO to return the id not the browse record + Add condition on the button "Sending goods>Packing to be invoiced" to show + only customer packings + Fix zero division error when the quantity is zero on an invoice line + Fix duplicate timesheet line that have been invoiced + Fix invoice report for bad removeParentNode tag + Fix priority for product view + Fix tax line computation when encoding account lines manually + Fix refund supplier invoice to have the same journal + New chinese translation + Pass context to action_done on stock move + Add product_uom change on sale order line + Fix demo data for working time UOM + Fix _sheet function in timesheet_sheet when called with a list of non + unique id + Remove commit inside function validate on account move + Use one function to post account move + Fix computation of sale/purchase amount in segmentation module + Use standar uom converion in analytic lines + Add journal_id in context for account move line search in payment module + Fix wrong id used by pricelist based on partner form + Use partner reference from SO/PO for invoice name if there is one + Make analysis analytic module include child accounts + +4.2.0 + Summary: + Add new view graph + REPORT_INTRASTAT: new module + KERNEL: add netrpc (speed improvement) + REPORT_STOCK: add report on stock by stock location and production lots + HR_TIMESHEET_INVOICE: add final invoice + MULTI_COMPANY_ACCOUNT: new module + ADD modules publication tools + KERNEL: add timezone + KERNEL: add concurnecy check + BASE: allow to specify many view_id in act_window + BASE: add ir.rules (acces base on record fields) + KERNEL: add search_count on objects + KERNEL: add assert tools (unit test) + KERNEL: improve workflow speed + KERNEL: move some modules to extra_addons + Bugfixes: + Fix pooler for multi-db + REPORT_ANALYTIC: new reports + BOARD_ACCOUNT: new dashboard for accountants + PURCHASE: allow multiple pickings for the same purchase order + STOCK: When refunding picking: confirm & Assign the newly generated picking + PRODUCT: add average price + STOCK: Fix workflow for stock + TOOLS: Fix export translate for wizard + KERNEL: add id in import_data + BASE: add history rate to currency + ACCOUNT: partner_id is now required for an invoice + HR_TIMESHEET: add exception if employee haven't product + I18N: new fr_CH file + HR_EXPENSE: fix domain + ACCOUNT: Fix invoice with currency and payment term + ACCOUNT: Fix currency + KERNEL: add pidfile + ACCOUNT,PURCHASE,SALE: use partner lang for description + Model Acces: Unlink permission (delete) is now available + KERNEL: Remove set for python2.3 + HR: add id to Attendance menu + PRODUCT: add dimension to packaging + ACCOUNT: new cash_discount on payment term + KERNEL: Add price accuracy + BASE: Function to remove installed modules + REPORT_SALE: fix for sale without line + PURCHASE: remove use of currency + KERNEL: fix set without values + PURCHASE: fix domain pricelist + INVOICE: use date for currency rate + KERNEL: Fix import many2many by id + KERNEL: run the cron + ACCOUNT: bank statment line now have a ref t othe corresponding invoice + ACCOUNT: Add possibilitty to include tax amount in base amount for the computation of the next taxes + ACCOUNT: Add product in tax compute python code + KERNEL: use reportlab 2.0 + BASE: fix import the same lang + ACCOUNT: fix tax code + ACCOUNT: define tax account for invoice and refund + ACCOUNT: add supplier tax to product + ACCOUNT: don't overwrite tax_code on the creation for account line + PURCHASE: use partner code for report order + KERNEL: fix pooler netsvc for multi-db + TOOLS: add ref to function tag + PRODUCT: fix digits on volume and weight, add weight_net + ACCOUNT: split to new module account_cash_discount + ORM : error message on python constraints are now displayed correctly + ACCOUNT: add partner to tax compute context + KERNEL: improve logger + PROJECT: add check_recursion for project + HR_TIMESHEET_INVOICE: improve create invoice + ACCOUNT: add product_id to analytic line create by invoice + KERNEL: fix the inheritance mechanism + KERNEL: Fix use always basename for cvs file + BASE: fix IBAN len to 27 + INVOICE: fix invoice number for analytic + REPORT: add replace tag for custom header + ACCOUNT: add ref to analytic line + BASE: prevent exception in ir_cron + SALE: fix uos for tax_amount + MRP: fix dbname in _procure_confirm + HR_EXPENSE: add domain to analytic_account + KERNEL: use 0 instead of False for fix on _fnct_read + SUBSCRIPTION: add required to model + HR_TIMESHEET: add rounding on report + SALE: Fix cancel invoice and recreate invoice, now cancel also the order lines + STOCK-DELIVERY: add wizard invoice_onshipping from delivery to stock + STOCK: use tax from sale for invoice + BASE: improve copy of res.partner + ACCOUNT: pay only invoice if not in state draft + REPORT: fix rml translation, translate before eval + PRODUCT_EXTENDED: don't use seller price for bom price + ACCOUNT_TAX_INCLUDE: fix right amount in account move generate with tax_include + BASE: improve workflow print + SALE: fix workflow error when create invoice from wizard + MRP: Use company currency for Product Cost Structure + BASE: prevent recursion in company + KERNEL: Fix deleted property and many2one + KERNEL: allow directory for import csv + KERNEL: add store option to fields function + ACCOUNT: use property_account_tax on on_change_product + KERNEL: add right-click for translate label + KERNEL: fix log of backtrace + KERNEL: fix search on xxx2many + BASE: use tool to call popen.pipe2 + KERNEL: fix print workflow on win32 + BASE: fix US states + KERNEL: use python 2.3 format_exception + ACCOUNT: add multi-company into base accounting + KERNEL: check return code for exec_pg_command_pipe + KERNEL: fix search with active args + KERNEL: improve _sql_contsraints, now insert if doesn't exist + KERNEL: remove old inheritor and add _constraints and _sql_constraints to the fields inherited + CRM: bugfix mailgate + PURCHASE: fix the UOM for purchase line and improve update price unit + ACCOUNT: new invoice view + KERNEL,BASE: allow to create zip modules + BASE: add right-to-left + KERNEL: copy now ignore technical values ('create_date', 'create_uid', 'write_date' and 'write_uid') + ACCOUNT_TAX_INCLUDE: Now the module manage correctly the case when the taxes defined on the product differ from the taxes defined on the invoice line + ALL: fix colspan 3 -> 4 + KERNEL: use context for search + ACCOUNT: improve speed of analytic account + ACCOUNT: fix search debit/credit on partner + ACCOUNT: fix refund invoice if no product_id nor uos_id on lines + MRP: fix scheduler location of product to produce and method, date of automatic orderpoint + KERNEL: many2many : fix unlink and link action + MRP: add default product_uom from context and add link from product to bom + PROJECT: improve speed for function fields + ALL: remove bad act_window name + KERNEL: modification for compatibility with postgres 7.4 + KERNEL: fix size for selection field + KERNEL: fix compatibility for python2.5 + KERNEL: add new win32 build script + KERNEL: add test for duplicate report and wizard + ACCOUNT: force round amount fixed in payment term + KERNEL: fix print screen + CRM: Better ergonomy + SERVER: add sum tag on tree view that display sum of the selected lines + KERNEL: allow subfield query on one2many + KERNEL: fix create_date and write_date as there are timestamp now + SERVER: improve language + KERNEL: fix search on fields function of type one2many, many2many + ACCOUNT: fix pay invoice to use period + ACCOUNT: add check recursion in account.tax.code + MRP: fix compute cycle for workcenter + BASE: add constraint uniq module name + BASE: improve update module list + ACCOUNT: add round to last payment term + KERNEL: don't modify the args of the call + KERNEL: don't use mutable as default value in function defintion + KERNEL: fix orm for sql query with reserved words + +16/03/2007 +4.0.3 + Summary: + Improve the migration scripts + Some bugfixes + Print workflow on win32 (with ghostscript) + + Bugfixes: + BASE: Fix "set default value" + HR_TIMESHEET_INVOICE: Improve invoice on timesheet + ACCOUNT: Fix tax amount + KERNEL: correct the delete for property + PURCHASE: fix the journal for invoice created by PO + KERNEL: fix the migration for id removed + Add id to some menuitem + BASE: prevent exception in ir_cron when the DB is dropped + HR: Fix sign-in/sign-out, the user is now allowed to provide a date in + the future + SALE: fix uos for the tax amount + MRP: fix wrong dbname in _procure_confirm + HR_EXPENSE: add domain to analytic_account + ACCOUNT: fix debit_get + SUBSCRIPTION: model is required now + HR_TIMESHEET: add rounding value to report + SALE: Fix cancel and recreate invoice, now cancel also the order lines + STOCK: use the tax define in sale for the invoice + ACCOUNT: add test to pay only if invoice not in state draft + KERNEL: root have access to all records + REPORT: fix rml translation to translate before the eval + ACCOUNT_TAX_INCLUDE: Use the right amount in account mmove generate + with tax_include + BASE: Improve the workflow print + SALE: Fix workflow error when creating invoice from the wizard + PRODUCT_EXTENDED: don't use pricelist to compute standard price + MRP: Use company currency for product cost structure + KERNEL: fix where clause when deleting false items + ACCOUNT: product source account depend on the invoice type now + ACCOUNT: use the property account tax for the on_change_product + ACCOUNT: use the invoice date for the date of analytic line + ACCOUNT: Fix the pay invoice when multi-currency + HR_TIMESHEET_PROJECT: use the right product + STOCK: Fix to assign picking with product consumable and call the + workflow + STOCK: Fix the split lot production + PURCHASE: fix workflow for purchase with manual invoice to not set + invoice and paid + DELIVERY: can use any type of journal for invoice + KERNEL: fix search on xxx2many + ACCOUNT: add id to sequence record + KERNEL: set properly the demo flag for module installed + KERNEL: Fix print workflow on win32 + LETTER: fix print letter + + Migration: + Fix migration for postreSQL 7.4 + Fix the default value of demo in module + Fix migration of account_uos to product_uos + +Wed Jan 17 15:06:07 CET 2007 +4.0.2 + Summary: + Improve the migration + Some bugfixes + Improve tax + + Bugfixes: + Fix tax for invoice, refund, etc + SALE: fix view priority + PURCHASE: wizard may crash on some data + BASE: Fix import the same lang + BASE: start the cron + PURCHASE: fix domain for pricelist + KERNEL: fix object set without values + REPORT_SALE: fix for sale without line + KERNEL: add pidfile + BASE: remove 'set' for python2.3 compliant + Migration: + Migrate hr_timesheet user_id + +Fri Dec 22 12:01:26 CET 2006 +4.0.1 + Summary: + Improve the migration + Some bugfixes + + Bugfixes: + HR_EXPENSE: Fix domain + HR_TIMESHEET: Fix employee without product + TOOLS: Fix export translate + BASE: fix for concurrency of sequence number + MRP: fix report + CRM: fix graph report + KERNEL: fix instance of osv_pool + KERNEL: fix setup.py + + +Mon Dec 4 18:01:55 CET 2006 +4.0.0 + Summary: + Some bugfixes + +Tue Nov 28 14:44:20 CET 2006 +4.0.0-rc1 + Summary: + This is a stable version (RC1) with lots of new features. Main + Improvements were: + Accounting: more functions, new modules, more stable + Much more better ergonomy + Lots of simplification to allows non IT people to use and + configure Tiny ERP: manage database, step by step configuration + menu, auto-installers, better help, ... + + New: + Skill management module + ACCOUNT: + New and simpler bank statement form + New reports: + on Timesheets (analytic accounting) + Theorical revenue based on time spent + Global timesheet report by month + Chart of accounts + Different taxes methods supported + Gross (brut) + Net + Fixed amount + INVOICE: + invoice on shipping (manufacturing industry) + invoice on timesheet (services) + PURCHASE: + different invoicing control method (on order, on shipping, + manual) + Support of prices tax included /excluded in sales orders + New modules: + Sale_journal, stock_journal for bigger industries: + Divide works in different journals + New invoicing method from partner, to so, to picking + Daily, Monthly (grouped by partner or not) + New modules for prices with taxes included / excluded + New chart of accounts supported: + l10n_be/ l10n_chart_be_frnl/ + l10n_chart_id/ l10n_chart_uk/ + l10n_ca-qc/ l10n_chart_br/ + l10n_chart_it/ l10n_chart_us_general/ + l10n_ch/ l10n_chart_ca_en/ + l10n_chart_it_cc2424/ l10n_chart_us_manufacturing/ + l10n_ch_pcpbl_association/ l10n_chart_ca_fr/ + l10n_chart_la/ l10n_chart_us_service/ + l10n_ch_pcpbl_independant/ l10n_chart_ch_german/ + l10n_chart_nl/ l10n_chart_us_ucoa/ + l10n_ch_pcpbl_menage/ l10n_chart_cn/ + l10n_chart_nl_standard/ l10n_chart_us_ucoa_ez/ + l10n_ch_pcpbl_plangen/ l10n_chart_cn_traditional/ + l10n_chart_no/ l10n_chart_ve/ + l10n_ch_pcpbl_plangensimpl/ l10n_chart_co/ + l10n_chart_pa/ l10n_fr/ + l10n_ch_vat_brut/ l10n_chart_cz/ + l10n_chart_pl/ l10n_se/ + l10n_ch_vat_forfait/ l10n_chart_da/ + l10n_chart_sp/ l10n_simple/ + l10n_ch_vat_net/ l10n_chart_de_datev_skr03/ + l10n_chart_sw/ + l10n_chart_at/ l10n_chart_de_skr03/ + l10n_chart_sw_church/ + l10n_chart_au/ l10n_chart_hu/ + l10n_chart_sw_food/ + Step by step configuration menu + Setup wizard on first connection + Select a company profile, auto-install language, demo data, ... + + Imrovements: + KERNEL: Demo data improved + Better import / export system + KERNEL: Multi-database management system + Backup, Restore, Create, Drop from the client + PRODUCT/PRODUCT_EXTD: Eavily change the product form, use the new + object to compute the pricelist + REPORTS: + Better Sale order, purchase order, invocies and customers reports + ACCOUNT: Support of taxes in accounts + management of the VAT taxes for most european countries: + Support of VAT codes in invoices + Better computation of default values in accounting entries + Preferences in partners, override products + Bugfix when closing a fiscal year + Better ergonomy when writting entries + New Module Management System: + Install / Upgrade new modules directly from the client + Install new languages + KERNEL: + Ability to add select=True at the object level for postgresql indexes + Bugfix in search in some inherited objects + Added the ability to call methods from a browse object + KERNEL+BASE: changed the way the migration system works for menuitems: + now you can change a menuitem defined elsewhere. And this will work + whether that menuitem has an id or not (it use the name of the + menuitem to find it) + KERNEL: + Installing a module from the client + Better Windows Auto-Installer + DELIVERY: + Delivery and invoicing on picking list + KERNEL: + Distinction between active (by default) and installable + ACCOUNT/PROJECT: Added support for the type of invoicing + CRM: + eMAil gateway + Management of different departments and sections + Rule system + About 20 new statistics reporting + eCommerce interface: + Better Joomla (virtuemart, OSCommerce) support + Joomla is now fully functionnal + + Bugfixes: + ACCOUNT: tree view on reporting analytic account + KERNEL: Fix the bug that happened when mixing active and child_of + search + KERNEL: Check for the existance of active when computing child_of + PRODUCT: production computation with different UoM + +------------------------------------------------------------------------ + +Fri Oct 6 14:44:05 CEST 2006 +Server 3.4.2 + Improvements: + BASE: changed workflow print system so that it handles inexisting + workflows more gracefully (patch from Geoff Gardiner) + MRP: new view to take into account the orderpoint exceptions + MRP: made menu title more explicit + + Bugfixes: + ACCOUNT: fixed typo in invoice + changed sxw file so that it is in + sync with the rml file + DELIVERY: fixed taxes on delivery line (patch from Brice Vissière) + PROJECT: skip tasks without user in Gantt charts (it crashed the report) + PRODUCT: fixed bug when no active pricelist version was found + PRODUCT_EXTENDED: correct recursive computation of the price + SALE: get product price from price list even when quantity is set after + the product is set + STOCK: fixed partial picking + + Packaging: + Changed migration script so that it works on PostgreSQL 7.4 + +------------------------------------------------------------------------ + +Tue Sep 12 15:10:31 CEST 2006 +Server 3.4.1 + Bugfixes: + ACCOUNT: fixed a bug which prevented to reconcile posted moves. + +------------------------------------------------------------------------ + +Mon Sep 11 16:12:10 CEST 2006 +Server 3.4.0 (changes since 3.3.0) + New modules: + ESALE_JOOMLA: integration with Joomla CMS + HR_TIMESHEET_ICAL: import iCal to automatically complete timesheet + based on outlook meetings + PARTNER_LDAP: adds partner synchronization with an LDAP server + SALE_REBATE: adds rebates to sale orders + + 4 new modules for reporting using postgresql views: + REPORT_CRM: reporting on CRM cases: by month, user, ... + REPORT_PROJECT: reporting on projects: tasks closed by project, user, + month, ... + REPORT_PURCHASE: reporting on purchases + REPORT_SALE: reporting on sales by periods and by product, category of + product, ... + + New features: + KERNEL: Tiny ERP server and client may now communicate through HTTPS. + To launch the server with HTTPS, use the -S or --secure option + Note that if the server runs on HTTPS, the clients MUST connect + with the "secure" option checked. + KERNEL: the server can now run as a service on Windows + Printscreen function (Tree view print) + KERNEL: added a new --stop-after-init option which stops the server + just before it starts listening + KERNEL: added support for a new forcecreate attribute on XML record + fields: it is useful for records are in a data node marked as + "noupdate" but the record still needs to be added if it doesn't + exit yet. The typical use for that is when you add a new record + to a noupdate file/node. + KERNEL: manage SQL constraints with human-readable error message on the + client side, eg: Unique constraints + KERNEL: added a new system to be able to specify the tooltip for each + field in the definition of the field (by using the new help="" + attribute) + ACCOUNT: new report: aged trial balance system + ACCOUNT: added a wizard to pay an invoice from the invoice form + BASE: print on a module to print the reference guide using introspection + HR: added report on attendance errors + PRODUCT: products now support multi-Level variants + + Improvements: + KERNEL: speed improvement in many parts of the system thanks to some + optimizations and a new caching system + KERNEL: New property system which replace the, now deprecated, ir_set + system. This leads to better migration of properties, more + practical use of them (they can be used like normal fields), + they can be translated, they are "multi-company aware", and + you can specify access rights for them on a per field basis. + KERNEL: Under windows, the server looks for its configuration file in + the "etc" sub directory (relative to the installation path). + This was needed so that the server can be run as a windows + service (using the SYSTEM profile). + KERNEL: added ability to import CSV files from the __terp__.py file + KERNEL: force freeing cursor when closing them, so that they are + available again immediately and not when garbage collected. + KERNEL: automatically drop not null/required constraints from removed + fields (ie which are in the database but not in the object) + KERNEL: added a command-line option to specify which smtp server to use + to send emails. + KERNEL: made browse_record hashable + ALL: removed shortcuts for the demo user. + ACCOUNT: better invoice report + ACCOUNT: Modifs for account chart, removed old stock_income account type + ACCOUNT: made the test_paid method on invoices more tolerant to buggy + data (open invoices without move/movelines) + ACCOUNT: better bank statement reconciliation system + ACCOUNT: accounting entries encoding improved a lot (using journal) + ACCOUNT: Adding a date and max Qty field in analytic accounts for + support contract + ACCOUNT: Adding the View type to analytic account / cost account + ACCOUNT: changed test_paid so that the workflow works even if there is + no move line + ACCOUNT: Cleanup credit/debit and balance computation methods. Should + be faster too. + ACCOUNT: use the normal sequence (from the journal) for the name of + moves generated from invoices instead of the longer name. + ACCOUNT: print Payment delay in invoices + ACCOUNT: account chart show subtotals + ACCOUNT: Subtotal in view accounts + ACCOUNT: Replaced some Typo: moves-> entries, Transaction -> entry + ACCOUNT: added quantities in analytic accounts view, and modified + cost ledger report for partners/customers + ACCOUNT: added default value for the currency field in invoices + ACCOUNT: added the comment/notes field on the invoice report + BASE: added menuitem (and action) to access partner functions (in the + definitions menu) + BASE: better demo data + BASE: duplicating a menu item now duplicates its action and submenus + BASE: Bank Details on Partners + CRM: View on all actions made on cases (used by our ISO9002 customer + to manage corrections to actions) + CRM: fixed wizard to create a sale order from a case + CRM: search on non active case, not desactivated by default + CRM: Case ID in fields with search + HR_TIMESHEET: new "sign_in, sign_out" using projects. It fills + timesheets and attendance at the same time. + HR_TIMESHEET: added cost unit to employee demo data + MRP: improvement in the scheduler + MRP: purchase order lines' description generated from a procurement + defaults to the product name instead of procurement name + MRP: Better traceability + MRP: Better view for procurement in exception + MRP: Added production delay in product forms. Use this delay for + average production delay for one product + MRP: dates scheduler, better computation + MRP: added constraint for non 0 BoM lines + PRODUCT: Better pricelist system (on template or variant of product) + PRODUCT_EXTENDED: Compute the price only if there is a supplier + PROJECT: when a task is closed, use the task's customer to warn the + customer if it is set, otherwise use the project contact. + PROJECT: better system to automatically send an email to the customer + when a task is closed or reopened. + PURCHASE: date_planned <= current_time line in red + PURCHASE: better purchase order report + PURCHASE: better purchase order duplication: you can now duplicate non + draft purchase orders and the new one will become draft. + SALE: better sale order report + SALE: better demo data for sale orders + SALE: better view for buttons in sale.order + SALE: select product => description = product name instead of code + SALE: warehouse field in shop is now required + SCRUM: lots of improvements for better useability + STOCK: allows to confirm empty picking lists. + STOCK: speed up stock computation methods + + Bugfixes: + KERNEL: fix a huge bug in the search method for objects involving + "old-style" inheritance (inherits) which prevented some records + to be accessible in some cases. Most notable example was some + products were not accessible in the sale order lines if you had + more products in your database than the limit of your search + (80 by default). + KERNEL: fixed bug which caused OO (sxw) reports to behave badly (crash + on Windows and not print correctly on Linux) when data + contained XML entities (&, <, >) + KERNEL: reports are now fully concurrency compliant + KERNEL: fixed bug which caused menuitems without id to cause havoc on + update. The menuitems themselves were not created (which is + correct) but they created a bad "default" action for all + menuitems without action (such as all "menu folders"). + KERNEL: fix a small security issue: we should check the password of the + user when a user asks for the result of a report (in addition + to the user id and id of that report) + KERNEL: bugfix in view inheritancy + KERNEL: fixed duplicating resource with a state field whose selection + doesn't contain a 'draft' value (for example project tasks). It + now uses the default value of the resource for that field. + KERNEL: fixed updating many2many fields using the (4, id) syntax + KERNEL: load/save the --logfile option correctly in the config file + KERNEL: fixed duplicating a resource with many2many fields + ALL: all properties should be inside a data tag with "noupdate" and + should have a forcecreate attribute. + ACCOUNT: fixed rounding bug in tax computation method + ACCOUNT: bugfix in balance and aged balance reports + ACCOUNT: fixing precision in function fields methods + ACCOUNT: fixed creation of account move lines without using the client + interface + ACCOUNT: fixed duplicating invoices + ACCOUNT: fixed opening an invoices whose description contained non + ASCII chars at specific position + ACCOUNT: small bugfixes in all accounting reports + ACCOUNT: fixed crash when --without-demo due to missing payment.term + ACCOUNT: fixed bug in automatic reconciliation + ACCOUNT: pass the address to the tax computation method so that it is + available in the tax "python applicable code" + BASE: allows to delete a request which has a history (it now deletes the + history as well as the request) + BASE: override copy method for users so that we can duplicate them + BASE: fixed bug when the user search for a partner by hitting on an + empty many2one field (it searched for a partner with ref=='') + BASE: making ir.sequence call thread-safe. + CRM: fixed a bug which introduced an invalid case state when closing a + case (Thanks to Leigh Willard) + HR: added domain to category tree view so that they are not displayed + twice + HR_TIMESHEET: fixed print graph + HR_TIMESHEET: fixed printing timesheet report + HR_TIMESHEET: Remove a timesheet entry removes the analytic line + MRP: bugfix on "force reservation" + MRP: fixed bugs in some reports and MRP scheduler when a partner has + no address + MRP: fix Force production button if no product available + MRP: when computing lots of procurements, the scheduler could raise + locking error at the database level. Fixed. + PRODUCT: added missing context to compute product list price + PRODUCT: fixed field type of qty_available and virtual_available + (integer->float). This prevented these fields to be displayed + in forms. + PROJECT: fixed the view of unassigned task (form and list) instead of + form only. + PURCHASE: fixed merging orders that made inventory errors when coming + from a procurement (orderpoint). + PURCHASE: fix bug which prevented to make a purchase order with + "manual" lines (ie without product) + PURCHASE: fix wizard to group purchase orders in several ways: + - only group orders if they are to the same location + - only group lines if they are the same except for qty and unit + - fix the workflow redirect method so that procurement are not + canceled when we merge orders + SALE: fixed duplicating a confirmed sale order + SALE: fixed making sale orders with "manual" lines (without product) + STOCK: future stock prevision bugfix (for move when date_planned < now) + STOCK: better view for stock.move + STOCK: fixed partial pickings (waiting for a production) + Miscellaneous minor bugfixes + + Packaging: + Fixed bug in setup.py which didn't copy csv files nor some sub- + directories + Added a script to migrate a 3.3.0 server to 3.4.0 (you should read the + README file in doc/migrate/3.3.0-3.4.0) + Removed OsCommerce module + +------------------------------------------------------------------------ + +Fri May 19 10:16:18 CEST 2006 +Server 3.3.0 + New features: + NEW MODULE: hr_timesheet_project + Automatically maps projects and tasks to analytic account + So that hours spent closing tasks are automatically encoded + KERNEL: Added a logfile and a pidfile option (patch from Dan Horak) + STOCK: Added support for revisions of tracking numbers + STOCK: Added support for revision of production lots + STOCK: Added a "splitting and tracking lines" wizard + PRODUCT_EXTENDED: Added a method to compute the cost of a product + automatically from the cost of its parts + + Improvements: + ALL: Small improvements in wizards (order of buttons) + PRODUCT: Remove packaging info from supplierinfo + PROJECT: Better task view (moved unused fields to other tab) + SALE: Keep formating for sale order lines' notes in the sale order report + + Bugfixes: + KERNEL: Fixed bug which caused field names with non ascii chars didn't work + in list mode on Windows + KERNEL: Fix concurrency issue with UpdatableStr with the use of + threading.local + KERNEL: Removed browse_record __unicode__ method... It made the sale order + report crash when using product names with non ASCII characters + KERNEL: Fixed bug which caused the translation export to fail when the server + was not launched from the directory its source is. + BASE: Updating a menuitem now takes care its parent menus + BASE: Fixed a cursor locking issue with updates + BASE: Fixed viewing sequence types as a tree/list + HR: Month field needs to be required in the "hours spent" report + PURCHASE: fixed group purchase order wizard: + - if there were orders from several different suppliers, it created a purchase + order for only the first supplier but canceled other orders, even those which + weren't merged in the created order (closes bugzilla #236) + - doesn't trash "manual" lines (ie lines with no product) + - pay attentions to unit factors when adding several lines together + MRP: fixed workcenter load report (prints only the selected workcenters) and + does't crash if the user didn't select all workcenters + + Miscellaneous: + Removed pydot from required dependencies + +------------------------------------------------------------------------ + +Server 3.3.0-rc1 +================ + +Changelog for Users +------------------- + +New module: OS Commerce + Integration with Tiny ERP and OS Commerce + Synchronisation 100% automated with eSale; + Import of categories of products + Export of products (with photos support) + Import of Orders (with the eslae module) + Export of stock level + Import of OSCommerce Taxes + Multiple shop allowed with different rules/products + Simple Installation + +New Module: HR_TIMESHEET + Management by affair, timesheets creates analytic entries in the + accounting to get costs and revenue of each affairs. Affairs are + structured in trees. + +New Module: Account Follow Up + Multi-Level and configurable Follows ups for the accounting module + +New module; Productivity Analysis of users + A module to compare productivity of users of Tiny ERP + Generic module, you can compare everything (sales, products, partners, + ...) + +New Modules for localisations: + Accounting localisations for be, ca, fr, de, ch, sw + Fix: corrected encoding (latin1 to utf8) of Swedish account tree XML file + +New Module - Sandwich + Allows employees to order the lunch + Keeps employees preferences + +New Module TOOLS: + Email automatic importation/integration in the ERP + +New Module EDI: + Import of EDI sale orders + Export of shippings + +Multi-Company: + Tiny ERP is now fully multi-company ! + New Company and configuration can be made in the client side. + +ACCOUNTING: + Better Entries > Standard Entries (Editable Tree, like in Excel) + Automatic creation of lines + Journal centralised or not + Counterpart of lines in one line or one counterpart per entry + Analytic accounting recoded from scratch + 5 new reports + Completly integrated with: + production, + hr_timesheet > Management by affairs + sales & purchases, + Tasks. + Added unreconciliation functionnalities + Added account tree fast rendering + Better tax computation system supporting worldwide specific countries + Better subscription system + Wizard to close a period + Wizard to clase a fiscal year + Very powerfull, simple and complete multi-currency system + in pricelists, sale order, purchases, ... + Added required fields in currencies (currency code) + Added decimal support + Better search on accounts (on code, shortcut or name) + Added constraint; + on users + on group + on accounts in a journal + added menuitem for automatic reconciliation; Multi-Levels + added factor to analytic units + added form view for budget items dotations + made number of digits in quantity field of the budget spread wizard coherent with the object field + fixed journal on purchase invoices/refunds (SugarCRM #6) + Better bank statement reconciliation + Fixed some reports + +STOCK: + Better view for location (using localisation of locations; posx, posy, posz) + +MARKETING: + fixed small bug when a partner has no adress + state field of marketing partner set as readonly + fixed marketing steps form view + better history view + disabled completely send sms wizard + fixed send email wizard + good priority -> high priority + fixed 'call again later' button + +NETWORK: + added tree view for login/password + +HR: + added holiday_status (=type of ...) to expense claim form view + +BASE (partner): + fixed email_send and _email_send methods + removed partner without addresses from demo data + Added a date field in the partner form + +MRP: + New report: workcenter futur loads + Analytic entries when production done. + SCHEDULER: better error msg in the generated request + Allows services in BoMs (for eg, subcontracting) + +Project/Service Management: + create orders from tasks; bugfixes + Completly integrated with the rest of the ERP + Services can now be MTO/MTS, Buy (subcontracting), produce (task), ... + Services can be used anywhere (sale.order, bom, ...) + See this graph; + http://tiny.be/download/flux/flux_procurement.png + tasks sorted by ... AND id, so that the order is not random + within a priority + +Automatic translations of all wizards + +Scrum Project Management + Better Ergonomy; click on a sprint to view tasks + Planned, Effetive hours and progress in backlog, project and sprint + Better Burndown Chart computation + Better (simpler) view of tasks + +Better demo Data + In All modules, eth converted to english + +PRODUCT: + computing the weight of the packaging + Added last order date + Alternative suppliers (with delay, prefs, ...) for one product + +PRICELISTS: + much more powerfull system + views simplified + one pricelist per usage: sale, order, pvc + price_type on product_view + Multi-Currency pricelist (EUR pricelist can depend on a $ one) + +HR-TIMESHEET: fixed bugs in hours report: + sum all lines for the same day instead of displaying only the first one + it now uses the analytic unit factor, so that mixing hours and days has some sense + close cursor + +SALE: + invoices generated from a sale order are pre-computed (taxes are computed) + + new invoicing functionnality; + invoice on order quantities or, + invoice on shipped quantities + + Invoice on a sale.order or a sale.order.line + + added default value for uos_qty in sale order lines (default to 1) + + +Changelog for Developers +------------------------ + +New option --debug, that opens a python interpreter when an exception +occurs on the server side. + +Better wizard system. Arguements self, cr, uid, context are passed in all +functions of the wizard like normal objects. All wizards converted. + +Speed improvements in many views; partners, sale.order, ... + less requests from client to server when opening a form + +Better translation system, wizard terms are exported. + +Script to render module dependency graph + +KERNEL+ALL: pass context to methods computing a selection. + +Modification for actions and view definitions: + Actions Window: + New field: view_mode = 'tree,form' or 'form,tree' -> default='form,tree' + New role of view_type: tree (with shortcuts), form (others with switch button) + If you need a form that opens in list mode: + view_mode = 'tree,form' or 'tree' + view_type = form + You can define a view in a view (for example sale.order.line in + sale.order) + less requests on the client side, no need to define 2 views + +Better command-line option message + +Fixed bug which prevented to search for names using non ASCII +chars in many2one or many2many fields + +Report Engine: bugfix for concurrency + +Support of SQL constraints + Uniq, check, ... + Good error message in the client side (check an account entry with + credit and debit >0) + +Fixed: when an exception was raised, the cursor wasn't closed and this +could cause a freeze in some cases + +Sequence can contains code: %(year)s, ... for prefix, suffix + EX: ORDER %(year)/0005 + +Bugfixes for automatic migration system + +bugfix on default value with creation of inherits + +Improvement in report_sxw; you can redefine preprocess to do some +preprocessing before printing + +Barcode support enabled by default + +Fixed OpenOffice reports when the server is not launched from the +directory the code reside + +Print workflow use a pipe instead of using a temporary file (now workflows +works on Windows Servers) + +Inheritancy improved (multiple arguments: replace, inside, after, before) + +Lots of small bugfixes + diff --git a/doc/README.urpmi b/doc/README.urpmi new file mode 100644 index 00000000000..ce8fb706703 --- /dev/null +++ b/doc/README.urpmi @@ -0,0 +1,36 @@ +Installation Steps +------------------ + +1. Check that all the required dependencies are installed. + +2. Create a postgresql database. + +The default database name is "terp". If you want to use another name, you +will need to provide it when launching the server (by using the commandline +option --database). + +To create a postgresql database named "terp" using the following command: + $ createdb --encoding=UNICODE terp + +If it is the first time you use postgresql you might need to create a new user +to the postgres system using the following commands (where myusername is your +unix user name): + + $ su - + # su - postgres + $ createuser openerp + Shall the new user be allowed to create databases? (y/n) y + Shall the new user be allowed to create more new users? (y/n) y + CREATE USER + $ logout + # logout + +3. Launch service daemon by "service openerp-server start". + +The first time it is run, the server will initialise the database with all the default values. + +4. Connect to the server using the GUI client. + +There are two accounts by default: + * login: admin, password:admin + * login: demo, password:demo diff --git a/doc/README.userchange b/doc/README.userchange new file mode 100644 index 00000000000..f6cc0ab0666 --- /dev/null +++ b/doc/README.userchange @@ -0,0 +1,31 @@ +Important note for OpenERP build >= 5.0.1-11xrg: + +THE USERNAME HAS CHANGED!! + +Former user "tinyerp" is now called "openerp". + + +For that, you will have to make sure the following files are chowned +to the new user: + +/var/log/openerp +/var/spool/openerp +/var/run/openerp +/etc/openerp +/etc/openerp/cert.cfg +/etc/openerp-server.conf +/etc/logrotate.d/openerp-server + +Then, rename the user in the postgres database: + + psql -U postgres postgres + + ALTER ROLE tinyerp RENAME TO openerp; + +Then, edit your openerp-server.conf to depict the change: +- db_user = tinyerp ++ db_user = openerp + +Good luck! + + diff --git a/doc/openerp-server.conf b/doc/openerp-server.conf new file mode 100644 index 00000000000..3b58a008329 --- /dev/null +++ b/doc/openerp-server.conf @@ -0,0 +1,27 @@ +[options] +without_demo = True +; This is the password that allows database operations: +; admin_passwd = admin +upgrade = False +verbose = False +netrpc = True +xmlrpc = True +port = 8069 +interface = +db_host = False +db_port = False +; Please uncomment the following line *after* you have created the +; database. It activates the auto module check on startup. +; db_name = terp +db_user = openerp +db_password = False +; Uncomment these for xml-rpc over SSL +; secure = True +; secure_cert_file = /etc/openerp/server.cert +; secure_pkey_file = /etc/openerp/server.key +root_path = None +soap = False +translate_modules = ['all'] +demo = {} +addons_path = None +reportgz = False diff --git a/doc/openerp-server.init b/doc/openerp-server.init new file mode 100644 index 00000000000..41efb5c35ce --- /dev/null +++ b/doc/openerp-server.init @@ -0,0 +1,147 @@ +#!/bin/bash +# openerp-server This shell script takes care of starting and stopping +# OpenERP server +# +# chkconfig: 345 95 05 +# description: OpenERP server +# +# pidfile: /var/run/openerp-server.pid +# config: /etc/openerp-server.conf + +### BEGIN INIT INFO +# Provides: openerp-server +# Required-Start: postgresql +# Required-Stop: postgresql +# Should-Start: $network harddrake +# Default-Start: 345 +# Short-Description: Launches the OpenERP server. +# Description: This startup script launches the OpenERP server. +### END INIT INFO + +# Source function library. +. /etc/rc.d/init.d/functions + +PIDFILE=/var/run/openerp/openerp-server.pid +LOCKFILE=/var/lock/subsys/openerp-server +LOGFILE=/var/log/openerp/openerp-server.log + +OPTS="--pidfile=$PIDFILE --logfile=$LOGFILE" + +prog="openerp-server" +desc="OpenERP Server Daemon" + +# check if the openerp-server conf file is present, then use it +if [ -f /etc/openerp-server.conf ]; then + OPTS="$OPTS -c /etc/openerp-server.conf" +fi + +# Source function library +if [ -f /etc/init.d/functions ] ; then + . /etc/init.d/functions +elif [ -f /etc/rc.d/init.d/functions ] ; then + . /etc/rc.d/init.d/functions +else + exit 0 +fi + +# check the existence of the openerp-server script +[ -z "/usr/bin/openerp-server" ] && exit 0 + +RETVAL=0 + +start() { + if [ -d /etc/openerp/start.d ] ; then + echo -n $"Preparing $desc: " + run-parts --exit-on-error /etc/openerp/start.d + RETVAL=$? + echo + [ $RETVAL -ne 0 ] && return $RETVAL + fi + + echo -n $"Starting $desc ($prog): " + daemon --user openerp --check openerp-server \ + "/usr/bin/setsid /usr/bin/openerp-server \ + -c /etc/openerp-server.conf \ + --pidfile=$PIDFILE \ + --logfile=$LOGFILE &" + + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && touch $LOCKFILE + return $RETVAL +} + +stop() { + echo -n $"Stopping $desc ($prog): " + kill -TERM `cat $PIDFILE` > /dev/null 2>&1 + RETVAL=$? + if [ $RETVAL -eq 0 ] ; then + rm -f $LOCKFILE + + echo_success + echo + else + echo_failure + echo + fi + if [ -d /etc/openerp/stop.d ] ; then + echo -n $"Clearing $desc: " + run-parts /etc/openerp/stop.d + echo + fi + return $RETVAL +} + +restart() { + stop + start +} + +condrestart() { + [ -e $LOCKFILE ] && restart || : +} + +status() { + if [ -f $PIDFILE ] ; then + checkpid `cat $PIDFILE` + RETVAL=$? + if [ $RETVAL -eq 0 ] ; then + echo $"$prog is running..." + else + echo $"$prog is stopped" + fi + else + echo $"$prog is stopped" + fi + return $RETVAL +} + +case "$1" in +start) + start + ;; + +stop) + stop + ;; + +restart|reload) + restart + ;; + +condrestart) + condrestart + ;; + +status) + status + ;; + +probe) + exit 0 + ;; + +*) + echo $"Usage: $0 {start|stop|status|restart|condrestart|reload}" + exit 1 +esac diff --git a/doc/openerp-server.logrotate b/doc/openerp-server.logrotate new file mode 100644 index 00000000000..e1f7f803a7d --- /dev/null +++ b/doc/openerp-server.logrotate @@ -0,0 +1,5 @@ +/var/log/openerp/*.log { + copytruncate + missingok + notifempty +} diff --git a/get-srvstats.sh b/get-srvstats.sh new file mode 100755 index 00000000000..4bb767ac18e --- /dev/null +++ b/get-srvstats.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# ADMIN_PASSWD='admin' +method_1() { + cat '-' << EOF + + + get_stats + + + +EOF +} +LEVEL=10 + +if [ -n "$1" ] ; then LEVEL=$1 ; fi + +method_1 $LEVEL | POST -c 'text/xml' http://localhost:8069/xmlrpc/common +#eof diff --git a/python25-compat/BaseHTTPServer.py b/python25-compat/BaseHTTPServer.py new file mode 100644 index 00000000000..5f2d558b689 --- /dev/null +++ b/python25-compat/BaseHTTPServer.py @@ -0,0 +1,587 @@ +"""HTTP server base class. + +Note: the class in this module doesn't implement any HTTP request; see +SimpleHTTPServer for simple implementations of GET, HEAD and POST +(including CGI scripts). It does, however, optionally implement HTTP/1.1 +persistent connections, as of version 0.3. + +Contents: + +- BaseHTTPRequestHandler: HTTP request handler base class +- test: test function + +XXX To do: + +- log requests even later (to capture byte count) +- log user-agent header and other interesting goodies +- send error log to separate file +""" + + +# See also: +# +# HTTP Working Group T. Berners-Lee +# INTERNET-DRAFT R. T. Fielding +# H. Frystyk Nielsen +# Expires September 8, 1995 March 8, 1995 +# +# URL: http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-v10-spec-00.txt +# +# and +# +# Network Working Group R. Fielding +# Request for Comments: 2616 et al +# Obsoletes: 2068 June 1999 +# Category: Standards Track +# +# URL: http://www.faqs.org/rfcs/rfc2616.html + +# Log files +# --------- +# +# Here's a quote from the NCSA httpd docs about log file format. +# +# | The logfile format is as follows. Each line consists of: +# | +# | host rfc931 authuser [DD/Mon/YYYY:hh:mm:ss] "request" ddd bbbb +# | +# | host: Either the DNS name or the IP number of the remote client +# | rfc931: Any information returned by identd for this person, +# | - otherwise. +# | authuser: If user sent a userid for authentication, the user name, +# | - otherwise. +# | DD: Day +# | Mon: Month (calendar name) +# | YYYY: Year +# | hh: hour (24-hour format, the machine's timezone) +# | mm: minutes +# | ss: seconds +# | request: The first line of the HTTP request as sent by the client. +# | ddd: the status code returned by the server, - if not available. +# | bbbb: the total number of bytes sent, +# | *not including the HTTP/1.0 header*, - if not available +# | +# | You can determine the name of the file accessed through request. +# +# (Actually, the latter is only true if you know the server configuration +# at the time the request was made!) + +__version__ = "0.3" + +__all__ = ["HTTPServer", "BaseHTTPRequestHandler"] + +import sys +import time +import socket # For gethostbyaddr() +import mimetools +import SocketServer + +# Default error message template +DEFAULT_ERROR_MESSAGE = """\ + +Error response + + +

Error response

+

Error code %(code)d. +

Message: %(message)s. +

Error code explanation: %(code)s = %(explain)s. + +""" + +DEFAULT_ERROR_CONTENT_TYPE = "text/html" + +def _quote_html(html): + return html.replace("&", "&").replace("<", "<").replace(">", ">") + +class HTTPServer(SocketServer.TCPServer): + + allow_reuse_address = 1 # Seems to make sense in testing environment + + def server_bind(self): + """Override server_bind to store the server name.""" + SocketServer.TCPServer.server_bind(self) + host, port = self.socket.getsockname()[:2] + self.server_name = socket.getfqdn(host) + self.server_port = port + + +class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler): + + """HTTP request handler base class. + + The following explanation of HTTP serves to guide you through the + code as well as to expose any misunderstandings I may have about + HTTP (so you don't need to read the code to figure out I'm wrong + :-). + + HTTP (HyperText Transfer Protocol) is an extensible protocol on + top of a reliable stream transport (e.g. TCP/IP). The protocol + recognizes three parts to a request: + + 1. One line identifying the request type and path + 2. An optional set of RFC-822-style headers + 3. An optional data part + + The headers and data are separated by a blank line. + + The first line of the request has the form + + + + where is a (case-sensitive) keyword such as GET or POST, + is a string containing path information for the request, + and should be the string "HTTP/1.0" or "HTTP/1.1". + is encoded using the URL encoding scheme (using %xx to signify + the ASCII character with hex code xx). + + The specification specifies that lines are separated by CRLF but + for compatibility with the widest range of clients recommends + servers also handle LF. Similarly, whitespace in the request line + is treated sensibly (allowing multiple spaces between components + and allowing trailing whitespace). + + Similarly, for output, lines ought to be separated by CRLF pairs + but most clients grok LF characters just fine. + + If the first line of the request has the form + + + + (i.e. is left out) then this is assumed to be an HTTP + 0.9 request; this form has no optional headers and data part and + the reply consists of just the data. + + The reply form of the HTTP 1.x protocol again has three parts: + + 1. One line giving the response code + 2. An optional set of RFC-822-style headers + 3. The data + + Again, the headers and data are separated by a blank line. + + The response code line has the form + + + + where is the protocol version ("HTTP/1.0" or "HTTP/1.1"), + is a 3-digit response code indicating success or + failure of the request, and is an optional + human-readable string explaining what the response code means. + + This server parses the request and the headers, and then calls a + function specific to the request type (). Specifically, + a request SPAM will be handled by a method do_SPAM(). If no + such method exists the server sends an error response to the + client. If it exists, it is called with no arguments: + + do_SPAM() + + Note that the request name is case sensitive (i.e. SPAM and spam + are different requests). + + The various request details are stored in instance variables: + + - client_address is the client IP address in the form (host, + port); + + - command, path and version are the broken-down request line; + + - headers is an instance of mimetools.Message (or a derived + class) containing the header information; + + - rfile is a file object open for reading positioned at the + start of the optional input data part; + + - wfile is a file object open for writing. + + IT IS IMPORTANT TO ADHERE TO THE PROTOCOL FOR WRITING! + + The first thing to be written must be the response line. Then + follow 0 or more header lines, then a blank line, and then the + actual data (if any). The meaning of the header lines depends on + the command executed by the server; in most cases, when data is + returned, there should be at least one header line of the form + + Content-type: / + + where and should be registered MIME types, + e.g. "text/html" or "text/plain". + + """ + + # The Python system version, truncated to its first component. + sys_version = "Python/" + sys.version.split()[0] + + # The server software version. You may want to override this. + # The format is multiple whitespace-separated strings, + # where each string is of the form name[/version]. + server_version = "BaseHTTP/" + __version__ + + # The default request version. This only affects responses up until + # the point where the request line is parsed, so it mainly decides what + # the client gets back when sending a malformed request line. + # Most web servers default to HTTP 0.9, i.e. don't send a status line. + default_request_version = "HTTP/0.9" + + def parse_request(self): + """Parse a request (internal). + + The request should be stored in self.raw_requestline; the results + are in self.command, self.path, self.request_version and + self.headers. + + Return True for success, False for failure; on failure, an + error is sent back. + + """ + self.command = None # set in case of error on the first line + self.request_version = version = self.default_request_version + self.close_connection = 1 + requestline = self.raw_requestline + if requestline[-2:] == '\r\n': + requestline = requestline[:-2] + elif requestline[-1:] == '\n': + requestline = requestline[:-1] + self.requestline = requestline + words = requestline.split() + if len(words) == 3: + [command, path, version] = words + if version[:5] != 'HTTP/': + self.send_error(400, "Bad request version (%r)" % version) + return False + try: + base_version_number = version.split('/', 1)[1] + version_number = base_version_number.split(".") + # RFC 2145 section 3.1 says there can be only one "." and + # - major and minor numbers MUST be treated as + # separate integers; + # - HTTP/2.4 is a lower version than HTTP/2.13, which in + # turn is lower than HTTP/12.3; + # - Leading zeros MUST be ignored by recipients. + if len(version_number) != 2: + raise ValueError + version_number = int(version_number[0]), int(version_number[1]) + except (ValueError, IndexError): + self.send_error(400, "Bad request version (%r)" % version) + return False + if version_number >= (1, 1) and self.protocol_version >= "HTTP/1.1": + self.close_connection = 0 + if version_number >= (2, 0): + self.send_error(505, + "Invalid HTTP Version (%s)" % base_version_number) + return False + elif len(words) == 2: + [command, path] = words + self.close_connection = 1 + if command != 'GET': + self.send_error(400, + "Bad HTTP/0.9 request type (%r)" % command) + return False + elif not words: + return False + else: + self.send_error(400, "Bad request syntax (%r)" % requestline) + return False + self.command, self.path, self.request_version = command, path, version + + # Examine the headers and look for a Connection directive + self.headers = self.MessageClass(self.rfile, 0) + + conntype = self.headers.get('Connection', "") + if conntype.lower() == 'close': + self.close_connection = 1 + elif (conntype.lower() == 'keep-alive' and + self.protocol_version >= "HTTP/1.1"): + self.close_connection = 0 + return True + + def handle_one_request(self): + """Handle a single HTTP request. + + You normally don't need to override this method; see the class + __doc__ string for information on how to handle specific HTTP + commands such as GET and POST. + + """ + self.raw_requestline = self.rfile.readline() + if not self.raw_requestline: + self.close_connection = 1 + return + if not self.parse_request(): # An error code has been sent, just exit + return + mname = 'do_' + self.command + if not hasattr(self, mname): + self.send_error(501, "Unsupported method (%r)" % self.command) + return + method = getattr(self, mname) + method() + + def handle(self): + """Handle multiple requests if necessary.""" + self.close_connection = 1 + + self.handle_one_request() + while not self.close_connection: + self.handle_one_request() + + def send_error(self, code, message=None): + """Send and log an error reply. + + Arguments are the error code, and a detailed message. + The detailed message defaults to the short entry matching the + response code. + + This sends an error response (so it must be called before any + output has been generated), logs the error, and finally sends + a piece of HTML explaining the error to the user. + + """ + + try: + short, long = self.responses[code] + except KeyError: + short, long = '???', '???' + if message is None: + message = short + explain = long + self.log_error("code %d, message %s", code, message) + # using _quote_html to prevent Cross Site Scripting attacks (see bug #1100201) + content = (self.error_message_format % + {'code': code, 'message': _quote_html(message), 'explain': explain}) + self.send_response(code, message) + self.send_header("Content-Type", self.error_content_type) + self.send_header('Connection', 'close') + self.end_headers() + if self.command != 'HEAD' and code >= 200 and code not in (204, 304): + self.wfile.write(content) + + error_message_format = DEFAULT_ERROR_MESSAGE + error_content_type = DEFAULT_ERROR_CONTENT_TYPE + + def send_response(self, code, message=None): + """Send the response header and log the response code. + + Also send two standard headers with the server software + version and the current date. + + """ + self.log_request(code) + if message is None: + if code in self.responses: + message = self.responses[code][0] + else: + message = '' + if self.request_version != 'HTTP/0.9': + self.wfile.write("%s %d %s\r\n" % + (self.protocol_version, code, message)) + # print (self.protocol_version, code, message) + self.send_header('Server', self.version_string()) + self.send_header('Date', self.date_time_string()) + + def send_header(self, keyword, value): + """Send a MIME header.""" + if self.request_version != 'HTTP/0.9': + self.wfile.write("%s: %s\r\n" % (keyword, value)) + + if keyword.lower() == 'connection': + if value.lower() == 'close': + self.close_connection = 1 + elif value.lower() == 'keep-alive': + self.close_connection = 0 + + def end_headers(self): + """Send the blank line ending the MIME headers.""" + if self.request_version != 'HTTP/0.9': + self.wfile.write("\r\n") + + def log_request(self, code='-', size='-'): + """Log an accepted request. + + This is called by send_response(). + + """ + + self.log_message('"%s" %s %s', + self.requestline, str(code), str(size)) + + def log_error(self, format, *args): + """Log an error. + + This is called when a request cannot be fulfilled. By + default it passes the message on to log_message(). + + Arguments are the same as for log_message(). + + XXX This should go to the separate error log. + + """ + + self.log_message(format, *args) + + def log_message(self, format, *args): + """Log an arbitrary message. + + This is used by all other logging functions. Override + it if you have specific logging wishes. + + The first argument, FORMAT, is a format string for the + message to be logged. If the format string contains + any % escapes requiring parameters, they should be + specified as subsequent arguments (it's just like + printf!). + + The client host and current date/time are prefixed to + every message. + + """ + + sys.stderr.write("%s - - [%s] %s\n" % + (self.address_string(), + self.log_date_time_string(), + format%args)) + + def version_string(self): + """Return the server software version string.""" + return self.server_version + ' ' + self.sys_version + + def date_time_string(self, timestamp=None): + """Return the current date and time formatted for a message header.""" + if timestamp is None: + timestamp = time.time() + year, month, day, hh, mm, ss, wd, y, z = time.gmtime(timestamp) + s = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % ( + self.weekdayname[wd], + day, self.monthname[month], year, + hh, mm, ss) + return s + + def log_date_time_string(self): + """Return the current time formatted for logging.""" + now = time.time() + year, month, day, hh, mm, ss, x, y, z = time.localtime(now) + s = "%02d/%3s/%04d %02d:%02d:%02d" % ( + day, self.monthname[month], year, hh, mm, ss) + return s + + weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] + + monthname = [None, + 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + + def address_string(self): + """Return the client address formatted for logging. + + This version looks up the full hostname using gethostbyaddr(), + and tries to find a name that contains at least one dot. + + """ + + host, port = self.client_address[:2] + return socket.getfqdn(host) + + # Essentially static class variables + + # The version of the HTTP protocol we support. + # Set this to HTTP/1.1 to enable automatic keepalive + protocol_version = "HTTP/1.0" + + # The Message-like class used to parse headers + MessageClass = mimetools.Message + + # Table mapping response codes to messages; entries have the + # form {code: (shortmessage, longmessage)}. + # See RFC 2616. + responses = { + 100: ('Continue', 'Request received, please continue'), + 101: ('Switching Protocols', + 'Switching to new protocol; obey Upgrade header'), + + 200: ('OK', 'Request fulfilled, document follows'), + 201: ('Created', 'Document created, URL follows'), + 202: ('Accepted', + 'Request accepted, processing continues off-line'), + 203: ('Non-Authoritative Information', 'Request fulfilled from cache'), + 204: ('No Content', 'Request fulfilled, nothing follows'), + 205: ('Reset Content', 'Clear input form for further input.'), + 206: ('Partial Content', 'Partial content follows.'), + + 300: ('Multiple Choices', + 'Object has several resources -- see URI list'), + 301: ('Moved Permanently', 'Object moved permanently -- see URI list'), + 302: ('Found', 'Object moved temporarily -- see URI list'), + 303: ('See Other', 'Object moved -- see Method and URL list'), + 304: ('Not Modified', + 'Document has not changed since given time'), + 305: ('Use Proxy', + 'You must use proxy specified in Location to access this ' + 'resource.'), + 307: ('Temporary Redirect', + 'Object moved temporarily -- see URI list'), + + 400: ('Bad Request', + 'Bad request syntax or unsupported method'), + 401: ('Unauthorized', + 'No permission -- see authorization schemes'), + 402: ('Payment Required', + 'No payment -- see charging schemes'), + 403: ('Forbidden', + 'Request forbidden -- authorization will not help'), + 404: ('Not Found', 'Nothing matches the given URI'), + 405: ('Method Not Allowed', + 'Specified method is invalid for this server.'), + 406: ('Not Acceptable', 'URI not available in preferred format.'), + 407: ('Proxy Authentication Required', 'You must authenticate with ' + 'this proxy before proceeding.'), + 408: ('Request Timeout', 'Request timed out; try again later.'), + 409: ('Conflict', 'Request conflict.'), + 410: ('Gone', + 'URI no longer exists and has been permanently removed.'), + 411: ('Length Required', 'Client must specify Content-Length.'), + 412: ('Precondition Failed', 'Precondition in headers is false.'), + 413: ('Request Entity Too Large', 'Entity is too large.'), + 414: ('Request-URI Too Long', 'URI is too long.'), + 415: ('Unsupported Media Type', 'Entity body in unsupported format.'), + 416: ('Requested Range Not Satisfiable', + 'Cannot satisfy request range.'), + 417: ('Expectation Failed', + 'Expect condition could not be satisfied.'), + + 500: ('Internal Server Error', 'Server got itself in trouble'), + 501: ('Not Implemented', + 'Server does not support this operation'), + 502: ('Bad Gateway', 'Invalid responses from another server/proxy.'), + 503: ('Service Unavailable', + 'The server cannot process the request due to a high load'), + 504: ('Gateway Timeout', + 'The gateway server did not receive a timely response'), + 505: ('HTTP Version Not Supported', 'Cannot fulfill request.'), + } + + +def test(HandlerClass = BaseHTTPRequestHandler, + ServerClass = HTTPServer, protocol="HTTP/1.0"): + """Test the HTTP request handler class. + + This runs an HTTP server on port 8000 (or the first command line + argument). + + """ + + if sys.argv[1:]: + port = int(sys.argv[1]) + else: + port = 8000 + server_address = ('', port) + + HandlerClass.protocol_version = protocol + httpd = ServerClass(server_address, HandlerClass) + + sa = httpd.socket.getsockname() + print "Serving HTTP on", sa[0], "port", sa[1], "..." + httpd.serve_forever() + + +if __name__ == '__main__': + test() diff --git a/python25-compat/SimpleXMLRPCServer.py b/python25-compat/SimpleXMLRPCServer.py new file mode 100644 index 00000000000..43757a03dda --- /dev/null +++ b/python25-compat/SimpleXMLRPCServer.py @@ -0,0 +1,611 @@ +"""Simple XML-RPC Server. + +This module can be used to create simple XML-RPC servers +by creating a server and either installing functions, a +class instance, or by extending the SimpleXMLRPCServer +class. + +It can also be used to handle XML-RPC requests in a CGI +environment using CGIXMLRPCRequestHandler. + +A list of possible usage patterns follows: + +1. Install functions: + +server = SimpleXMLRPCServer(("localhost", 8000)) +server.register_function(pow) +server.register_function(lambda x,y: x+y, 'add') +server.serve_forever() + +2. Install an instance: + +class MyFuncs: + def __init__(self): + # make all of the string functions available through + # string.func_name + import string + self.string = string + def _listMethods(self): + # implement this method so that system.listMethods + # knows to advertise the strings methods + return list_public_methods(self) + \ + ['string.' + method for method in list_public_methods(self.string)] + def pow(self, x, y): return pow(x, y) + def add(self, x, y) : return x + y + +server = SimpleXMLRPCServer(("localhost", 8000)) +server.register_introspection_functions() +server.register_instance(MyFuncs()) +server.serve_forever() + +3. Install an instance with custom dispatch method: + +class Math: + def _listMethods(self): + # this method must be present for system.listMethods + # to work + return ['add', 'pow'] + def _methodHelp(self, method): + # this method must be present for system.methodHelp + # to work + if method == 'add': + return "add(2,3) => 5" + elif method == 'pow': + return "pow(x, y[, z]) => number" + else: + # By convention, return empty + # string if no help is available + return "" + def _dispatch(self, method, params): + if method == 'pow': + return pow(*params) + elif method == 'add': + return params[0] + params[1] + else: + raise 'bad method' + +server = SimpleXMLRPCServer(("localhost", 8000)) +server.register_introspection_functions() +server.register_instance(Math()) +server.serve_forever() + +4. Subclass SimpleXMLRPCServer: + +class MathServer(SimpleXMLRPCServer): + def _dispatch(self, method, params): + try: + # We are forcing the 'export_' prefix on methods that are + # callable through XML-RPC to prevent potential security + # problems + func = getattr(self, 'export_' + method) + except AttributeError: + raise Exception('method "%s" is not supported' % method) + else: + return func(*params) + + def export_add(self, x, y): + return x + y + +server = MathServer(("localhost", 8000)) +server.serve_forever() + +5. CGI script: + +server = CGIXMLRPCRequestHandler() +server.register_function(pow) +server.handle_request() +""" + +# Written by Brian Quinlan (brian@sweetapp.com). +# Based on code written by Fredrik Lundh. + +import xmlrpclib +from xmlrpclib import Fault +import SocketServer +import BaseHTTPServer +import sys +import os +import traceback +try: + import fcntl +except ImportError: + fcntl = None + +def resolve_dotted_attribute(obj, attr, allow_dotted_names=True): + """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d + + Resolves a dotted attribute name to an object. Raises + an AttributeError if any attribute in the chain starts with a '_'. + + If the optional allow_dotted_names argument is false, dots are not + supported and this function operates similar to getattr(obj, attr). + """ + + if allow_dotted_names: + attrs = attr.split('.') + else: + attrs = [attr] + + for i in attrs: + if i.startswith('_'): + raise AttributeError( + 'attempt to access private attribute "%s"' % i + ) + else: + obj = getattr(obj,i) + return obj + +def list_public_methods(obj): + """Returns a list of attribute strings, found in the specified + object, which represent callable attributes""" + + return [member for member in dir(obj) + if not member.startswith('_') and + hasattr(getattr(obj, member), '__call__')] + +def remove_duplicates(lst): + """remove_duplicates([2,2,2,1,3,3]) => [3,1,2] + + Returns a copy of a list without duplicates. Every list + item must be hashable and the order of the items in the + resulting list is not defined. + """ + u = {} + for x in lst: + u[x] = 1 + + return u.keys() + +class SimpleXMLRPCDispatcher: + """Mix-in class that dispatches XML-RPC requests. + + This class is used to register XML-RPC method handlers + and then to dispatch them. There should never be any + reason to instantiate this class directly. + """ + + def __init__(self, allow_none, encoding): + self.funcs = {} + self.instance = None + self.allow_none = allow_none + self.encoding = encoding + + def register_instance(self, instance, allow_dotted_names=False): + """Registers an instance to respond to XML-RPC requests. + + Only one instance can be installed at a time. + + If the registered instance has a _dispatch method then that + method will be called with the name of the XML-RPC method and + its parameters as a tuple + e.g. instance._dispatch('add',(2,3)) + + If the registered instance does not have a _dispatch method + then the instance will be searched to find a matching method + and, if found, will be called. Methods beginning with an '_' + are considered private and will not be called by + SimpleXMLRPCServer. + + If a registered function matches a XML-RPC request, then it + will be called instead of the registered instance. + + If the optional allow_dotted_names argument is true and the + instance does not have a _dispatch method, method names + containing dots are supported and resolved, as long as none of + the name segments start with an '_'. + + *** SECURITY WARNING: *** + + Enabling the allow_dotted_names options allows intruders + to access your module's global variables and may allow + intruders to execute arbitrary code on your machine. Only + use this option on a secure, closed network. + + """ + + self.instance = instance + self.allow_dotted_names = allow_dotted_names + + def register_function(self, function, name = None): + """Registers a function to respond to XML-RPC requests. + + The optional name argument can be used to set a Unicode name + for the function. + """ + + if name is None: + name = function.__name__ + self.funcs[name] = function + + def register_introspection_functions(self): + """Registers the XML-RPC introspection methods in the system + namespace. + + see http://xmlrpc.usefulinc.com/doc/reserved.html + """ + + self.funcs.update({'system.listMethods' : self.system_listMethods, + 'system.methodSignature' : self.system_methodSignature, + 'system.methodHelp' : self.system_methodHelp}) + + def register_multicall_functions(self): + """Registers the XML-RPC multicall method in the system + namespace. + + see http://www.xmlrpc.com/discuss/msgReader$1208""" + + self.funcs.update({'system.multicall' : self.system_multicall}) + + def _marshaled_dispatch(self, data, dispatch_method = None): + """Dispatches an XML-RPC method from marshalled (XML) data. + + XML-RPC methods are dispatched from the marshalled (XML) data + using the _dispatch method and the result is returned as + marshalled data. For backwards compatibility, a dispatch + function can be provided as an argument (see comment in + SimpleXMLRPCRequestHandler.do_POST) but overriding the + existing method through subclassing is the prefered means + of changing method dispatch behavior. + """ + + try: + params, method = xmlrpclib.loads(data) + + # generate response + if dispatch_method is not None: + response = dispatch_method(method, params) + else: + response = self._dispatch(method, params) + # wrap response in a singleton tuple + response = (response,) + response = xmlrpclib.dumps(response, methodresponse=1, + allow_none=self.allow_none, encoding=self.encoding) + except Fault, fault: + response = xmlrpclib.dumps(fault, allow_none=self.allow_none, + encoding=self.encoding) + except: + # report exception back to server + exc_type, exc_value, exc_tb = sys.exc_info() + response = xmlrpclib.dumps( + xmlrpclib.Fault(1, "%s:%s" % (exc_type, exc_value)), + encoding=self.encoding, allow_none=self.allow_none, + ) + + return response + + def system_listMethods(self): + """system.listMethods() => ['add', 'subtract', 'multiple'] + + Returns a list of the methods supported by the server.""" + + methods = self.funcs.keys() + if self.instance is not None: + # Instance can implement _listMethod to return a list of + # methods + if hasattr(self.instance, '_listMethods'): + methods = remove_duplicates( + methods + self.instance._listMethods() + ) + # if the instance has a _dispatch method then we + # don't have enough information to provide a list + # of methods + elif not hasattr(self.instance, '_dispatch'): + methods = remove_duplicates( + methods + list_public_methods(self.instance) + ) + methods.sort() + return methods + + def system_methodSignature(self, method_name): + """system.methodSignature('add') => [double, int, int] + + Returns a list describing the signature of the method. In the + above example, the add method takes two integers as arguments + and returns a double result. + + This server does NOT support system.methodSignature.""" + + # See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html + + return 'signatures not supported' + + def system_methodHelp(self, method_name): + """system.methodHelp('add') => "Adds two integers together" + + Returns a string containing documentation for the specified method.""" + + method = None + if method_name in self.funcs: + method = self.funcs[method_name] + elif self.instance is not None: + # Instance can implement _methodHelp to return help for a method + if hasattr(self.instance, '_methodHelp'): + return self.instance._methodHelp(method_name) + # if the instance has a _dispatch method then we + # don't have enough information to provide help + elif not hasattr(self.instance, '_dispatch'): + try: + method = resolve_dotted_attribute( + self.instance, + method_name, + self.allow_dotted_names + ) + except AttributeError: + pass + + # Note that we aren't checking that the method actually + # be a callable object of some kind + if method is None: + return "" + else: + import pydoc + return pydoc.getdoc(method) + + def system_multicall(self, call_list): + """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \ +[[4], ...] + + Allows the caller to package multiple XML-RPC calls into a single + request. + + See http://www.xmlrpc.com/discuss/msgReader$1208 + """ + + results = [] + for call in call_list: + method_name = call['methodName'] + params = call['params'] + + try: + # XXX A marshalling error in any response will fail the entire + # multicall. If someone cares they should fix this. + results.append([self._dispatch(method_name, params)]) + except Fault, fault: + results.append( + {'faultCode' : fault.faultCode, + 'faultString' : fault.faultString} + ) + except: + exc_type, exc_value, exc_tb = sys.exc_info() + results.append( + {'faultCode' : 1, + 'faultString' : "%s:%s" % (exc_type, exc_value)} + ) + return results + + def _dispatch(self, method, params): + """Dispatches the XML-RPC method. + + XML-RPC calls are forwarded to a registered function that + matches the called XML-RPC method name. If no such function + exists then the call is forwarded to the registered instance, + if available. + + If the registered instance has a _dispatch method then that + method will be called with the name of the XML-RPC method and + its parameters as a tuple + e.g. instance._dispatch('add',(2,3)) + + If the registered instance does not have a _dispatch method + then the instance will be searched to find a matching method + and, if found, will be called. + + Methods beginning with an '_' are considered private and will + not be called. + """ + + func = None + try: + # check to see if a matching function has been registered + func = self.funcs[method] + except KeyError: + if self.instance is not None: + # check for a _dispatch method + if hasattr(self.instance, '_dispatch'): + return self.instance._dispatch(method, params) + else: + # call instance method directly + try: + func = resolve_dotted_attribute( + self.instance, + method, + self.allow_dotted_names + ) + except AttributeError: + pass + + if func is not None: + return func(*params) + else: + raise Exception('method "%s" is not supported' % method) + +class SimpleXMLRPCRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): + """Simple XML-RPC request handler class. + + Handles all HTTP POST requests and attempts to decode them as + XML-RPC requests. + """ + + # Class attribute listing the accessible path components; + # paths not on this list will result in a 404 error. + rpc_paths = ('/', '/RPC2') + + def is_rpc_path_valid(self): + if self.rpc_paths: + return self.path in self.rpc_paths + else: + # If .rpc_paths is empty, just assume all paths are legal + return True + + def do_POST(self): + """Handles the HTTP POST request. + + Attempts to interpret all HTTP POST requests as XML-RPC calls, + which are forwarded to the server's _dispatch method for handling. + """ + + # Check that the path is legal + if not self.is_rpc_path_valid(): + self.report_404() + return + + try: + # Get arguments by reading body of request. + # We read this in chunks to avoid straining + # socket.read(); around the 10 or 15Mb mark, some platforms + # begin to have problems (bug #792570). + max_chunk_size = 10*1024*1024 + size_remaining = int(self.headers["content-length"]) + L = [] + while size_remaining: + chunk_size = min(size_remaining, max_chunk_size) + L.append(self.rfile.read(chunk_size)) + size_remaining -= len(L[-1]) + data = ''.join(L) + + # In previous versions of SimpleXMLRPCServer, _dispatch + # could be overridden in this class, instead of in + # SimpleXMLRPCDispatcher. To maintain backwards compatibility, + # check to see if a subclass implements _dispatch and dispatch + # using that method if present. + response = self.server._marshaled_dispatch( + data, getattr(self, '_dispatch', None) + ) + except Exception, e: # This should only happen if the module is buggy + # internal error, report as HTTP server error + self.send_response(500) + + # Send information about the exception if requested + if hasattr(self.server, '_send_traceback_header') and \ + self.server._send_traceback_header: + self.send_header("X-exception", str(e)) + self.send_header("X-traceback", traceback.format_exc()) + + self.end_headers() + else: + # got a valid XML RPC response + self.send_response(200) + self.send_header("Content-type", "text/xml") + self.send_header("Content-length", str(len(response))) + self.end_headers() + self.wfile.write(response) + + # shut down the connection + self.wfile.flush() + self.connection.shutdown(1) + + def report_404 (self): + # Report a 404 error + self.send_response(404) + response = 'No such page' + self.send_header("Content-type", "text/plain") + self.send_header("Content-length", str(len(response))) + self.end_headers() + self.wfile.write(response) + # shut down the connection + self.wfile.flush() + self.connection.shutdown(1) + + def log_request(self, code='-', size='-'): + """Selectively log an accepted request.""" + + if self.server.logRequests: + BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size) + +class SimpleXMLRPCServer(SocketServer.TCPServer, + SimpleXMLRPCDispatcher): + """Simple XML-RPC server. + + Simple XML-RPC server that allows functions and a single instance + to be installed to handle requests. The default implementation + attempts to dispatch XML-RPC calls to the functions or instance + installed in the server. Override the _dispatch method inhereted + from SimpleXMLRPCDispatcher to change this behavior. + """ + + allow_reuse_address = True + + # Warning: this is for debugging purposes only! Never set this to True in + # production code, as will be sending out sensitive information (exception + # and stack trace details) when exceptions are raised inside + # SimpleXMLRPCRequestHandler.do_POST + _send_traceback_header = False + + def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler, + logRequests=True, allow_none=False, encoding=None, bind_and_activate=True): + self.logRequests = logRequests + + SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding) + SocketServer.TCPServer.__init__(self, addr, requestHandler, bind_and_activate) + + # [Bug #1222790] If possible, set close-on-exec flag; if a + # method spawns a subprocess, the subprocess shouldn't have + # the listening socket open. + if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'): + flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD) + flags |= fcntl.FD_CLOEXEC + fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags) + +class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher): + """Simple handler for XML-RPC data passed through CGI.""" + + def __init__(self, allow_none=False, encoding=None): + SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding) + + def handle_xmlrpc(self, request_text): + """Handle a single XML-RPC request""" + + response = self._marshaled_dispatch(request_text) + + print 'Content-Type: text/xml' + print 'Content-Length: %d' % len(response) + print + sys.stdout.write(response) + + def handle_get(self): + """Handle a single HTTP GET request. + + Default implementation indicates an error because + XML-RPC uses the POST method. + """ + + code = 400 + message, explain = \ + BaseHTTPServer.BaseHTTPRequestHandler.responses[code] + + response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \ + { + 'code' : code, + 'message' : message, + 'explain' : explain + } + print 'Status: %d %s' % (code, message) + print 'Content-Type: text/html' + print 'Content-Length: %d' % len(response) + print + sys.stdout.write(response) + + def handle_request(self, request_text = None): + """Handle a single XML-RPC request passed through a CGI post method. + + If no XML data is given then it is read from stdin. The resulting + XML-RPC response is printed to stdout along with the correct HTTP + headers. + """ + + if request_text is None and \ + os.environ.get('REQUEST_METHOD', None) == 'GET': + self.handle_get() + else: + # POST data is normally available through stdin + if request_text is None: + request_text = sys.stdin.read() + + self.handle_xmlrpc(request_text) + +if __name__ == '__main__': + print 'Running XML-RPC server on port 8000' + server = SimpleXMLRPCServer(("localhost", 8000)) + server.register_function(pow) + server.register_function(lambda x,y: x+y, 'add') + server.serve_forever() diff --git a/python25-compat/SocketServer.py b/python25-compat/SocketServer.py new file mode 100644 index 00000000000..2c41fbb6dc0 --- /dev/null +++ b/python25-compat/SocketServer.py @@ -0,0 +1,681 @@ +"""Generic socket server classes. + +This module tries to capture the various aspects of defining a server: + +For socket-based servers: + +- address family: + - AF_INET{,6}: IP (Internet Protocol) sockets (default) + - AF_UNIX: Unix domain sockets + - others, e.g. AF_DECNET are conceivable (see +- socket type: + - SOCK_STREAM (reliable stream, e.g. TCP) + - SOCK_DGRAM (datagrams, e.g. UDP) + +For request-based servers (including socket-based): + +- client address verification before further looking at the request + (This is actually a hook for any processing that needs to look + at the request before anything else, e.g. logging) +- how to handle multiple requests: + - synchronous (one request is handled at a time) + - forking (each request is handled by a new process) + - threading (each request is handled by a new thread) + +The classes in this module favor the server type that is simplest to +write: a synchronous TCP/IP server. This is bad class design, but +save some typing. (There's also the issue that a deep class hierarchy +slows down method lookups.) + +There are five classes in an inheritance diagram, four of which represent +synchronous servers of four types: + + +------------+ + | BaseServer | + +------------+ + | + v + +-----------+ +------------------+ + | TCPServer |------->| UnixStreamServer | + +-----------+ +------------------+ + | + v + +-----------+ +--------------------+ + | UDPServer |------->| UnixDatagramServer | + +-----------+ +--------------------+ + +Note that UnixDatagramServer derives from UDPServer, not from +UnixStreamServer -- the only difference between an IP and a Unix +stream server is the address family, which is simply repeated in both +unix server classes. + +Forking and threading versions of each type of server can be created +using the ForkingMixIn and ThreadingMixIn mix-in classes. For +instance, a threading UDP server class is created as follows: + + class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass + +The Mix-in class must come first, since it overrides a method defined +in UDPServer! Setting the various member variables also changes +the behavior of the underlying server mechanism. + +To implement a service, you must derive a class from +BaseRequestHandler and redefine its handle() method. You can then run +various versions of the service by combining one of the server classes +with your request handler class. + +The request handler class must be different for datagram or stream +services. This can be hidden by using the request handler +subclasses StreamRequestHandler or DatagramRequestHandler. + +Of course, you still have to use your head! + +For instance, it makes no sense to use a forking server if the service +contains state in memory that can be modified by requests (since the +modifications in the child process would never reach the initial state +kept in the parent process and passed to each child). In this case, +you can use a threading server, but you will probably have to use +locks to avoid two requests that come in nearly simultaneous to apply +conflicting changes to the server state. + +On the other hand, if you are building e.g. an HTTP server, where all +data is stored externally (e.g. in the file system), a synchronous +class will essentially render the service "deaf" while one request is +being handled -- which may be for a very long time if a client is slow +to reqd all the data it has requested. Here a threading or forking +server is appropriate. + +In some cases, it may be appropriate to process part of a request +synchronously, but to finish processing in a forked child depending on +the request data. This can be implemented by using a synchronous +server and doing an explicit fork in the request handler class +handle() method. + +Another approach to handling multiple simultaneous requests in an +environment that supports neither threads nor fork (or where these are +too expensive or inappropriate for the service) is to maintain an +explicit table of partially finished requests and to use select() to +decide which request to work on next (or whether to handle a new +incoming request). This is particularly important for stream services +where each client can potentially be connected for a long time (if +threads or subprocesses cannot be used). + +Future work: +- Standard classes for Sun RPC (which uses either UDP or TCP) +- Standard mix-in classes to implement various authentication + and encryption schemes +- Standard framework for select-based multiplexing + +XXX Open problems: +- What to do with out-of-band data? + +BaseServer: +- split generic "request" functionality out into BaseServer class. + Copyright (C) 2000 Luke Kenneth Casson Leighton + + example: read entries from a SQL database (requires overriding + get_request() to return a table entry from the database). + entry is processed by a RequestHandlerClass. + +""" + +# Author of the BaseServer patch: Luke Kenneth Casson Leighton + +# XXX Warning! +# There is a test suite for this module, but it cannot be run by the +# standard regression test. +# To run it manually, run Lib/test/test_socketserver.py. + +__version__ = "0.4" + + +import socket +import select +import sys +import os +try: + import threading +except ImportError: + import dummy_threading as threading + +__all__ = ["TCPServer","UDPServer","ForkingUDPServer","ForkingTCPServer", + "ThreadingUDPServer","ThreadingTCPServer","BaseRequestHandler", + "StreamRequestHandler","DatagramRequestHandler", + "ThreadingMixIn", "ForkingMixIn"] +if hasattr(socket, "AF_UNIX"): + __all__.extend(["UnixStreamServer","UnixDatagramServer", + "ThreadingUnixStreamServer", + "ThreadingUnixDatagramServer"]) + +class BaseServer: + + """Base class for server classes. + + Methods for the caller: + + - __init__(server_address, RequestHandlerClass) + - serve_forever(poll_interval=0.5) + - shutdown() + - handle_request() # if you do not use serve_forever() + - fileno() -> int # for select() + + Methods that may be overridden: + + - server_bind() + - server_activate() + - get_request() -> request, client_address + - handle_timeout() + - verify_request(request, client_address) + - server_close() + - process_request(request, client_address) + - close_request(request) + - handle_error() + + Methods for derived classes: + + - finish_request(request, client_address) + + Class variables that may be overridden by derived classes or + instances: + + - timeout + - address_family + - socket_type + - allow_reuse_address + + Instance variables: + + - RequestHandlerClass + - socket + + """ + + timeout = None + + def __init__(self, server_address, RequestHandlerClass): + """Constructor. May be extended, do not override.""" + self.server_address = server_address + self.RequestHandlerClass = RequestHandlerClass + self.__is_shut_down = threading.Event() + self.__serving = False + + def server_activate(self): + """Called by constructor to activate the server. + + May be overridden. + + """ + pass + + def serve_forever(self, poll_interval=0.5): + """Handle one request at a time until shutdown. + + Polls for shutdown every poll_interval seconds. Ignores + self.timeout. If you need to do periodic tasks, do them in + another thread. + """ + self.__serving = True + self.__is_shut_down.clear() + while self.__serving: + # XXX: Consider using another file descriptor or + # connecting to the socket to wake this up instead of + # polling. Polling reduces our responsiveness to a + # shutdown request and wastes cpu at all other times. + r, w, e = select.select([self], [], [], poll_interval) + if r: + self._handle_request_noblock() + self.__is_shut_down.set() + + def shutdown(self): + """Stops the serve_forever loop. + + Blocks until the loop has finished. This must be called while + serve_forever() is running in another thread, or it will + deadlock. + """ + self.__serving = False + self.__is_shut_down.wait() + + # The distinction between handling, getting, processing and + # finishing a request is fairly arbitrary. Remember: + # + # - handle_request() is the top-level call. It calls + # select, get_request(), verify_request() and process_request() + # - get_request() is different for stream or datagram sockets + # - process_request() is the place that may fork a new process + # or create a new thread to finish the request + # - finish_request() instantiates the request handler class; + # this constructor will handle the request all by itself + + def handle_request(self): + """Handle one request, possibly blocking. + + Respects self.timeout. + """ + # Support people who used socket.settimeout() to escape + # handle_request before self.timeout was available. + timeout = self.socket.gettimeout() + if timeout is None: + timeout = self.timeout + elif self.timeout is not None: + timeout = min(timeout, self.timeout) + fd_sets = select.select([self], [], [], timeout) + if not fd_sets[0]: + self.handle_timeout() + return + self._handle_request_noblock() + + def _handle_request_noblock(self): + """Handle one request, without blocking. + + I assume that select.select has returned that the socket is + readable before this function was called, so there should be + no risk of blocking in get_request(). + """ + try: + request, client_address = self.get_request() + except socket.error: + return + if self.verify_request(request, client_address): + try: + self.process_request(request, client_address) + except: + self.handle_error(request, client_address) + self.close_request(request) + + def handle_timeout(self): + """Called if no new request arrives within self.timeout. + + Overridden by ForkingMixIn. + """ + pass + + def verify_request(self, request, client_address): + """Verify the request. May be overridden. + + Return True if we should proceed with this request. + + """ + return True + + def process_request(self, request, client_address): + """Call finish_request. + + Overridden by ForkingMixIn and ThreadingMixIn. + + """ + self.finish_request(request, client_address) + self.close_request(request) + + def server_close(self): + """Called to clean-up the server. + + May be overridden. + + """ + pass + + def finish_request(self, request, client_address): + """Finish one request by instantiating RequestHandlerClass.""" + self.RequestHandlerClass(request, client_address, self) + + def close_request(self, request): + """Called to clean up an individual request.""" + pass + + def handle_error(self, request, client_address): + """Handle an error gracefully. May be overridden. + + The default is to print a traceback and continue. + + """ + print '-'*40 + print 'Exception happened during processing of request from', + print client_address + import traceback + traceback.print_exc() # XXX But this goes to stderr! + print '-'*40 + + +class TCPServer(BaseServer): + + """Base class for various socket-based server classes. + + Defaults to synchronous IP stream (i.e., TCP). + + Methods for the caller: + + - __init__(server_address, RequestHandlerClass, bind_and_activate=True) + - serve_forever(poll_interval=0.5) + - shutdown() + - handle_request() # if you don't use serve_forever() + - fileno() -> int # for select() + + Methods that may be overridden: + + - server_bind() + - server_activate() + - get_request() -> request, client_address + - handle_timeout() + - verify_request(request, client_address) + - process_request(request, client_address) + - close_request(request) + - handle_error() + + Methods for derived classes: + + - finish_request(request, client_address) + + Class variables that may be overridden by derived classes or + instances: + + - timeout + - address_family + - socket_type + - request_queue_size (only for stream sockets) + - allow_reuse_address + + Instance variables: + + - server_address + - RequestHandlerClass + - socket + + """ + + address_family = socket.AF_INET + + socket_type = socket.SOCK_STREAM + + request_queue_size = 5 + + allow_reuse_address = False + + def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True): + """Constructor. May be extended, do not override.""" + BaseServer.__init__(self, server_address, RequestHandlerClass) + self.socket = socket.socket(self.address_family, + self.socket_type) + if bind_and_activate: + self.server_bind() + self.server_activate() + + def server_bind(self): + """Called by constructor to bind the socket. + + May be overridden. + + """ + if self.allow_reuse_address: + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.socket.bind(self.server_address) + self.server_address = self.socket.getsockname() + + def server_activate(self): + """Called by constructor to activate the server. + + May be overridden. + + """ + self.socket.listen(self.request_queue_size) + + def server_close(self): + """Called to clean-up the server. + + May be overridden. + + """ + self.socket.close() + + def fileno(self): + """Return socket file number. + + Interface required by select(). + + """ + return self.socket.fileno() + + def get_request(self): + """Get the request and client address from the socket. + + May be overridden. + + """ + return self.socket.accept() + + def close_request(self, request): + """Called to clean up an individual request.""" + request.close() + + +class UDPServer(TCPServer): + + """UDP server class.""" + + allow_reuse_address = False + + socket_type = socket.SOCK_DGRAM + + max_packet_size = 8192 + + def get_request(self): + data, client_addr = self.socket.recvfrom(self.max_packet_size) + return (data, self.socket), client_addr + + def server_activate(self): + # No need to call listen() for UDP. + pass + + def close_request(self, request): + # No need to close anything. + pass + +class ForkingMixIn: + + """Mix-in class to handle each request in a new process.""" + + timeout = 300 + active_children = None + max_children = 40 + + def collect_children(self): + """Internal routine to wait for children that have exited.""" + if self.active_children is None: return + while len(self.active_children) >= self.max_children: + # XXX: This will wait for any child process, not just ones + # spawned by this library. This could confuse other + # libraries that expect to be able to wait for their own + # children. + try: + pid, status = os.waitpid(0, options=0) + except os.error: + pid = None + if pid not in self.active_children: continue + self.active_children.remove(pid) + + # XXX: This loop runs more system calls than it ought + # to. There should be a way to put the active_children into a + # process group and then use os.waitpid(-pgid) to wait for any + # of that set, but I couldn't find a way to allocate pgids + # that couldn't collide. + for child in self.active_children: + try: + pid, status = os.waitpid(child, os.WNOHANG) + except os.error: + pid = None + if not pid: continue + try: + self.active_children.remove(pid) + except ValueError, e: + raise ValueError('%s. x=%d and list=%r' % (e.message, pid, + self.active_children)) + + def handle_timeout(self): + """Wait for zombies after self.timeout seconds of inactivity. + + May be extended, do not override. + """ + self.collect_children() + + def process_request(self, request, client_address): + """Fork a new subprocess to process the request.""" + self.collect_children() + pid = os.fork() + if pid: + # Parent process + if self.active_children is None: + self.active_children = [] + self.active_children.append(pid) + self.close_request(request) + return + else: + # Child process. + # This must never return, hence os._exit()! + try: + self.finish_request(request, client_address) + os._exit(0) + except: + try: + self.handle_error(request, client_address) + finally: + os._exit(1) + + +class ThreadingMixIn: + """Mix-in class to handle each request in a new thread.""" + + # Decides how threads will act upon termination of the + # main process + daemon_threads = False + + def process_request_thread(self, request, client_address): + """Same as in BaseServer but as a thread. + + In addition, exception handling is done here. + + """ + try: + self.finish_request(request, client_address) + self.close_request(request) + except: + self.handle_error(request, client_address) + self.close_request(request) + + def process_request(self, request, client_address): + """Start a new thread to process the request.""" + t = threading.Thread(target = self.process_request_thread, + args = (request, client_address)) + if self.daemon_threads: + t.setDaemon (1) + t.start() + + +class ForkingUDPServer(ForkingMixIn, UDPServer): pass +class ForkingTCPServer(ForkingMixIn, TCPServer): pass + +class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass +class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass + +if hasattr(socket, 'AF_UNIX'): + + class UnixStreamServer(TCPServer): + address_family = socket.AF_UNIX + + class UnixDatagramServer(UDPServer): + address_family = socket.AF_UNIX + + class ThreadingUnixStreamServer(ThreadingMixIn, UnixStreamServer): pass + + class ThreadingUnixDatagramServer(ThreadingMixIn, UnixDatagramServer): pass + +class BaseRequestHandler: + + """Base class for request handler classes. + + This class is instantiated for each request to be handled. The + constructor sets the instance variables request, client_address + and server, and then calls the handle() method. To implement a + specific service, all you need to do is to derive a class which + defines a handle() method. + + The handle() method can find the request as self.request, the + client address as self.client_address, and the server (in case it + needs access to per-server information) as self.server. Since a + separate instance is created for each request, the handle() method + can define arbitrary other instance variariables. + + """ + + def __init__(self, request, client_address, server): + self.request = request + self.client_address = client_address + self.server = server + try: + self.setup() + self.handle() + self.finish() + finally: + sys.exc_traceback = None # Help garbage collection + + def setup(self): + pass + + def handle(self): + pass + + def finish(self): + pass + + +# The following two classes make it possible to use the same service +# class for stream or datagram servers. +# Each class sets up these instance variables: +# - rfile: a file object from which receives the request is read +# - wfile: a file object to which the reply is written +# When the handle() method returns, wfile is flushed properly + + +class StreamRequestHandler(BaseRequestHandler): + + """Define self.rfile and self.wfile for stream sockets.""" + + # Default buffer sizes for rfile, wfile. + # We default rfile to buffered because otherwise it could be + # really slow for large data (a getc() call per byte); we make + # wfile unbuffered because (a) often after a write() we want to + # read and we need to flush the line; (b) big writes to unbuffered + # files are typically optimized by stdio even when big reads + # aren't. + rbufsize = -1 + wbufsize = 0 + + def setup(self): + self.connection = self.request + self.rfile = self.connection.makefile('rb', self.rbufsize) + self.wfile = self.connection.makefile('wb', self.wbufsize) + + def finish(self): + if not self.wfile.closed: + self.wfile.flush() + self.wfile.close() + self.rfile.close() + + +class DatagramRequestHandler(BaseRequestHandler): + + # XXX Regrettably, I cannot get this working on Linux; + # s.recvfrom() doesn't return a meaningful client address. + + """Define self.rfile and self.wfile for datagram sockets.""" + + def setup(self): + try: + from cStringIO import StringIO + except ImportError: + from StringIO import StringIO + self.packet, self.socket = self.request + self.rfile = StringIO(self.packet) + self.wfile = StringIO() + + def finish(self): + self.socket.sendto(self.wfile.getvalue(), self.client_address) diff --git a/setup.py b/setup.py index 24398a8dcbf..bac72008235 100755 --- a/setup.py +++ b/setup.py @@ -73,11 +73,35 @@ def check_modules(): if not ok: sys.exit(1) -def find_addons(): +def _find_addons(): for (dp, dn, names) in os.walk(opj('bin', 'addons')): if '__terp__.py' in names: - modname = dp.replace(os.path.sep, '.').replace('bin', 'openerp-server', 1) - yield modname + modname = os.path.basename(dp) + yield (modname, dp) + #look for extra modules + try: + empath = os.getenv('EXTRA_MODULES_PATH','../addons/') + f = open(opj(empath,'server_modules.list'),'r') + # print 'Getting modules from:' , opj(empath,'server_modules.list') + mods = f.readlines() + for mname in mods: + mname = mname.strip() + if not mname: + continue + if os.path.exists(opj(empath,mname,'__terp__.py')): + yield ( mname, opj(empath,mname) ) + else: + print "Module %s specified, but no valid path." % mname + except: + pass + +__found_addons = None + +# Cache the results of _find_addons() and return them +def find_addons(found_addons = None): + if not found_addons: + found_addons = _find_addons() + return found_addons def data_files(): '''Build list of data files to be installed''' @@ -108,27 +132,40 @@ def data_files(): opj('bin', 'server.pkey'), opj('bin', 'server.cert')])) - for addon in find_addons(): - addonname = addon.split('.')[-1] - add_path = addon.replace('.', os.path.sep).replace('openerp-server', 'bin', 1) - addon_path = opj('lib', 'python%s' % py_short_version, 'site-packages', add_path.replace('bin', 'openerp-server', 1)) + if sys.version_info[0:2] == (2,5): + files.append((openerp_site_packages, [ opj('python25-compat','BaseHTTPServer.py'), + opj('python25-compat','SimpleXMLRPCServer.py'), + opj('python25-compat','SocketServer.py')])) + + for (addonname, add_path) in find_addons(): + addon_path = opj('lib', 'python%s' % py_short_version, 'site-packages', 'openerp-server','addons', addonname) pathfiles = [] for root, dirs, innerfiles in os.walk(add_path): - innerfiles = filter(lambda file: os.path.splitext(file)[1] not in ('.pyc', '.pyd', '.pyo'), innerfiles) + innerfiles = filter(lambda fil: os.path.splitext(fil)[1] not in ('.pyc', '.pyd', '.pyo'), innerfiles) if innerfiles: - res = os.path.normpath(opj(addon_path, root.replace(opj('bin','addons', addonname), '.'))) - pathfiles.extend(((res, map(lambda file: opj(root, file), innerfiles)),)) + res = os.path.normpath(opj(addon_path, root.replace(opj(add_path), '.'))) + pathfiles.extend(((res, map(lambda fil: opj(root, fil), innerfiles)),)) files.extend(pathfiles) + # for tup in files: + # print "Files:", tup[0], tup[1] return files -check_modules() +if not os.getenv('NO_CHECK_MODULES',False) : + check_modules() f = file('openerp-server','w') start_script = """#!/bin/sh\necho "OpenERP Setup - The content of this file is generated at the install stage\n" """ f.write(start_script) f.close() +def find_package_dirs(): + res = {} + for (mod, path) in find_addons(): + res ['openerp-server.addons.'+ mod ] = path + res ['openerp-server'] = 'bin' + return res + class openerp_server_install(install): def run(self): # create startup script @@ -179,12 +216,13 @@ setup(name = name, 'openerp-server.report.render', 'openerp-server.report.render.rml2pdf', 'openerp-server.report.render.rml2html', + 'openerp-server.report.render.rml2txt', + 'openerp-server.report.render.html2html', 'openerp-server.wizard', 'openerp-server.report.render.odt2odt', - 'openerp-server.report.render.html2html', 'openerp-server.workflow'] + \ - list(find_addons()), - package_dir = {'openerp-server': 'bin'}, + list(map( lambda (a, p): 'openerp-server.addons.'+ a ,find_addons())), + package_dir = find_package_dirs(), console = [ { "script" : "bin\\openerp-server.py", "icon_resources" : [ (1,"pixmaps\\openerp-icon.ico") ] } ], options = options, ) diff --git a/sql/clean-model.sql b/sql/clean-model.sql new file mode 100644 index 00000000000..59b492aab2d --- /dev/null +++ b/sql/clean-model.sql @@ -0,0 +1,24 @@ + +SELECT model, res_id, module FROM ir_model_data + WHERE model = 'ir.actions.act_window' + AND NOT EXISTS (SELECT 1 FROM ir_act_window WHERE id = ir_model_data.res_id); + + +SELECT model, res_id, module FROM ir_model_data + WHERE model = 'ir.ui.menu' + AND NOT EXISTS (SELECT 1 FROM ir_ui_menu WHERE id = ir_model_data.res_id); + +SELECT model, res_id, module FROM ir_model_data + WHERE model = 'ir.ui.view' + AND NOT EXISTS (SELECT 1 FROM ir_ui_view WHERE id = ir_model_data.res_id); + +DONT DELETE FROM ir_model_data + WHERE model = 'ir.actions.act_window' + AND NOT EXISTS (SELECT 1 FROM ir_act_window WHERE id = ir_model_data.res_id); + +DONT DELETE FROM ir_model_data + WHERE model = 'ir.ui.menu' + AND NOT EXISTS (SELECT 1 FROM ir_ui_menu WHERE id = ir_model_data.res_id); +-- Other cleanups: +-- DELETE from ir_model_data where module = 'audittrail' AND model = 'ir.ui.view' AND NOT EXISTS( SELECT 1 FROM ir_ui_view WHERE ir_ui_view.id = ir_model_data.res_id); +-- DELETE from ir_model_data where module = 'audittrail' AND model = 'ir.ui.menu' AND NOT EXISTS( SELECT 1 FROM ir_ui_menu WHERE id = ir_model_data.res_id); \ No newline at end of file