[merge] merge from lp:~xrg/openobject-server/optimize-5.0 and removed some confilts

bzr revid: hmo@tinyerp.com-20091120143104-tb6136unkdw7yfy9
This commit is contained in:
xrg 2009-11-20 20:01:04 +05:30 committed by Harry (Open ERP)
commit dad1ff955b
99 changed files with 8283 additions and 1093 deletions

View File

@ -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()
#

View File

@ -574,7 +574,7 @@
<field name="code">mh</field>
</record>
<record id="mk" model="res.country">
<field name="name">Macedonia</field>
<field name="name">FYROM</field>
<field name="code">mk</field>
</record>
<record id="ml" model="res.country">

File diff suppressed because it is too large Load Diff

View File

@ -114,6 +114,8 @@
<field name="padding"/>
<field name="number_increment"/>
<field name="number_next"/>
<field name="weight" />
<field name="condition" colspan="4" />
<separator colspan="4" string="Legend (for prefix, suffix)"/>
<group col="8" colspan="4">
<group>

View File

@ -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)'),

View File

@ -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)

View File

@ -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,11 +92,11 @@ 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:
return False
return False
try:
cr = db.cursor()
if not pool._init:
@ -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()

View File

@ -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,12 +47,15 @@ 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.")
}
_defaults = {
'active': lambda *a: True,
'number_increment': lambda *a: 1,
'number_next': lambda *a: 1,
'padding' : lambda *a : 0,
'weight' : lambda *a: 10,
}
def _process(self, s):
@ -70,10 +74,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'])
@ -83,8 +106,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()

View File

@ -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)

View File

@ -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,8 @@ 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',
ordering='out'
)
graph_instance_get(cr, graph, inst_id, data.get('nested', False))
ps_string = graph.create(prog='dot', format='ps')

View File

@ -233,6 +233,12 @@
<para style="terp_tblheader_Details">Object: [[ object.model ]] [[ objdoc(object.model) ]]</para>
</td>
</tr>
<tr>
<td>
<para style="terp_default_9">[[ repeatIn(objdoc2(object.model), 'sline') ]]</para>
<para style="terp_default_9"> [[ sline ]] </para>
</td>
</tr>
</blockTable>
<section>
<para style="terp_default_1">

View File

@ -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')])

View File

@ -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()

View File

@ -71,7 +71,7 @@
</record>
<record id="action_country_state" model="ir.actions.act_window">
<field name="name">States</field>
<field name="name">Fed. States</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">res.country.state</field>
<field name="view_type">form</field>

View File

@ -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

View File

@ -108,11 +108,11 @@ class res_company(osv.osv):
<pageGraphics>
<fill color="black"/>
<stroke color="black"/>
<setFont name="Helvetica" size="8"/>
<setFont name="DejaVu Sans" size="8"/>
<drawString x="1.3cm" y="28.3cm"> [[ formatLang(time.strftime("%Y-%m-%d"), date=True) ]] [[ time.strftime("%H:%M") ]]</drawString>
<setFont name="Helvetica-Bold" size="10"/>
<setFont name="DejaVu Sans Bold" size="10"/>
<drawString x="9.8cm" y="28.3cm">[[ company.partner_id.name ]]</drawString>
<setFont name="Helvetica" size="8"/>
<setFont name="DejaVu Sans" size="8"/>
<drawRightString x="19.7cm" y="28.3cm"><pageNumber/> / </drawRightString>
<drawString x="19.8cm" y="28.3cm"><pageCount/></drawString>
<stroke color="#000000"/>
@ -131,7 +131,7 @@ class res_company(osv.osv):
<pageGraphics>
<!-- You Logo - Change X,Y,Width and Height -->
<image x="1.3cm" y="27.6cm" height="40.0" >[[company.logo]]</image>
<setFont name="Helvetica" size="8"/>
<setFont name="DejaVu Sans" size="8"/>
<fill color="black"/>
<stroke color="black"/>
<lines>1.3cm 27.7cm 20cm 27.7cm</lines>

View File

@ -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()

View File

@ -12,6 +12,7 @@
<rng:choice>
<rng:ref name="notebook"/>
<rng:ref name="field"/>
<rng:ref name="shortcut"/>
<rng:ref name="group"/>
<rng:ref name="button"/>
<rng:ref name="hpaned" />
@ -44,6 +45,7 @@
<rng:zeroOrMore>
<rng:choice>
<rng:ref name="field"/>
<rng:ref name="shortcut"/>
<rng:ref name="separator"/>
<rng:ref name="tree"/>
<rng:ref name="group"/>
@ -184,6 +186,7 @@
<rng:element name="properties"><rng:empty/></rng:element>
<rng:ref name="label" />
<rng:ref name="field"/>
<rng:ref name="shortcut"/>
<rng:ref name="group"/>
<rng:ref name="separator"/>
<rng:ref name="button"/>
@ -222,6 +225,7 @@
<rng:ref name="separator"/>
<rng:ref name="button"/>
<rng:ref name="field"/>
<rng:ref name="shortcut"/>
<rng:ref name="label" />
<rng:ref name="group" />
<rng:ref name="filter"/>
@ -239,6 +243,7 @@
<rng:zeroOrMore>
<rng:choice>
<rng:ref name="any"/>
<rng:ref name="button"/>
</rng:choice>
</rng:zeroOrMore>
</rng:element>
@ -351,6 +356,7 @@
<rng:ref name="separator"/>
<rng:ref name="button"/>
<rng:ref name="field"/>
<rng:ref name="shortcut"/>
<rng:ref name="label" />
<rng:ref name="group" />
<rng:ref name="filter"/>
@ -473,6 +479,15 @@
</rng:element>
</rng:define>
<rng:define name="shortcut">
<rng:element name="shortcut">
<rng:optional><rng:attribute name="attrs"/></rng:optional>
<rng:optional><rng:attribute name="name" /></rng:optional>
<rng:optional><rng:attribute name="key"/></rng:optional>
<rng:optional><rng:attribute name="goto" /></rng:optional>
</rng:element>
</rng:define>
<rng:start>
<rng:choice>
<rng:ref name="form" />

View File

@ -96,6 +96,7 @@
<rng:attribute name="string"/>
<rng:attribute name="model"/>
<rng:attribute name="name"/>
<rng:optional><rng:attribute name="report_type"/></rng:optional>
<rng:optional><rng:attribute name="multi"/></rng:optional>
<rng:optional><rng:attribute name="menu"/></rng:optional>
<rng:optional><rng:attribute name="keyword"/></rng:optional>
@ -118,6 +119,7 @@
<rng:optional><rng:attribute name="type"/></rng:optional>
<rng:optional><rng:attribute name="ref"/></rng:optional>
<rng:optional><rng:attribute name="eval"/></rng:optional>
<rng:optional><rng:attribute name="domain"/></rng:optional>
<rng:optional><rng:attribute name="search"/></rng:optional>
<rng:optional><rng:attribute name="model"/></rng:optional>
<rng:optional><rng:attribute name="use"/></rng:optional>

View File

@ -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:

View File

@ -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:

View File

@ -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,8 +57,7 @@ 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)
' WHERE "%s" = ANY (%%s)' % (s, f, w), (subids,))
res.extend([r[0] for r in cr.fetchall()])
else:
cr.execute('SELECT distinct("%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)

View File

@ -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

View File

@ -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
@ -1337,8 +1349,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 = '''<?xml version="1.0" encoding="utf-8"?>''' \
'''<form string="%s">''' % (self._description,)
xml = '<?xml version="1.0" encoding="utf-8"?> ' \
'<form string="%s">' % (self._description,)
for x in res:
if res[x]['type'] not in ('one2many', 'many2many'):
xml += '<field name="%s"/>' % (x,)
@ -1349,19 +1361,27 @@ class orm_template(object):
_rec_name = self._rec_name
if _rec_name not in self._columns:
_rec_name = self._columns.keys()[0]
xml = '''<?xml version="1.0" encoding="utf-8"?>''' \
'''<tree string="%s"><field name="%s"/></tree>''' \
% (self._description, self._rec_name)
xml = '<?xml version="1.0" encoding="utf-8"?>' \
'<tree string="%s"><field name="%s"/></tree>' \
% (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 = '<?xml version="1.0"?>' # 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)
try:
doc = dom.minidom.parseString(encode(result['arch']))
except Exception, ex:
logger = netsvc.Logger()
logger.notifyChannel('init', netsvc.LOG_DEBUG, 'Wrong arch in %s (%s):\n %s' % (result['name'], view_type, result['arch'] ))
raise except_orm('Error',
('Invalid xml in view %s(%d) of %s: %s' % (result['name'], result['view_id'], self._name, str(ex))))
xarch, xfields = self.__view_look_dom_arch(cr, user, doc, view_id, context=context)
result['arch'] = xarch
result['fields'] = xfields
@ -1441,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:
@ -1462,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:
@ -1706,7 +1728,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):
@ -1882,6 +1904,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:
@ -1929,7 +1965,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()
@ -2033,7 +2069,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)))
@ -2243,9 +2279,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)
@ -2292,18 +2330,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)
@ -2313,6 +2349,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]
@ -2628,6 +2665,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])
@ -3145,18 +3183,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
@ -3205,7 +3245,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:

View File

@ -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:

View File

@ -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:

View File

@ -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)

View File

@ -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:

View File

@ -0,0 +1,49 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 P. Christeas, Tiny SPRL (<http://tiny.be>).
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
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 SetCustomFonts(rmldoc):
for name, font, fname, mode in CustomTTFonts:
rmldoc.setTTFontMapping(name, font,fname, mode)
#eof

View File

@ -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():
@ -775,6 +795,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 +808,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)

View File

@ -0,0 +1,26 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2008 Tiny SPRL (<http://tiny.be>). 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from rml2txt import parseString, parseNode
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,551 @@
#!/bin/env python
# -*- encoding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2008 Tiny SPRL (<http://tiny.be>). 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
# 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 '<table border="0" width="%d"><tr><td width="%d">&nbsp;</td><td>' % (self.width+self.posx,self.posx)
def tag_end(self):
return True
def tag_stop(self):
return "frame stop"
return '</td></tr></table><br/>'
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+='<td width="%d"></td><td style="%s" width="%d">%s</td>' % (x - posx, style, pos2, txt)
posx = x+pos2
if align=="right":
res+='<td width="%d" align="right" style="%s">%s</td>' % (x - posx, style, txt)
posx = x
if align=="center":
res+='<td width="%d" align="center" style="%s">%s</td>' % ((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 '<table border="0" cellpadding="0" cellspacing="0" width="%d"><tr><td width="%d"></td><td><hr width="100%%" style="margin:0px; %s"></td></tr></table>' % (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 = "<cannot render w/o template>"
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:

View File

@ -0,0 +1,149 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
# 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:

View File

@ -67,7 +67,8 @@ class simple(render.render):
if __name__=='__main__':
import time
s = simple('''<test>
s = simple()
s.xml = '''<test>
<author-list>
<author>
<name>Fabien Pinckaers</name>
@ -79,8 +80,9 @@ if __name__=='__main__':
</author>
No other
</author-list>
</test>''')
print s.render()
</test>'''
if s.render():
print s.get()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -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):

319
bin/service/http_server.py Normal file
View File

@ -0,0 +1,319 @@
# -*- encoding: utf-8 -*-
#
# Copyright P. Christeas <p_christ@hol.gr> 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

View File

@ -0,0 +1,163 @@
# -*- encoding: utf-8 -*-
#
# Copyright P. Christeas <p_christ@hol.gr> 2008,2009
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
""" 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))

View File

@ -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:

View File

@ -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
@ -130,8 +137,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), [])
@ -146,8 +152,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()
@ -179,8 +184,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()
@ -209,8 +213,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()
@ -260,8 +263,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()
@ -287,14 +289,14 @@ class db(netsvc.Service):
sql_db.close_db('template1')
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:
@ -323,27 +325,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:
@ -362,63 +362,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
@ -438,12 +449,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)
@ -456,8 +466,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:
@ -529,12 +538,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'
@ -553,43 +557,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)
return res
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()
@ -604,26 +613,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] = {}
@ -631,10 +650,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:
@ -658,22 +676,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
@ -729,8 +758,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:

423
bin/service/websrv_lib.py Normal file
View File

@ -0,0 +1,423 @@
# -*- encoding: utf-8 -*-
#
# Copyright P. Christeas <p_christ@hol.gr> 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("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
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 = """ <html><head><title>Authorization required</title></head>
<body>You must authenticate to use this service</body><html>\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

View File

@ -54,10 +54,12 @@ from mx import DateTime as mdt
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
sql_from_log = {}
@ -71,7 +73,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
@ -107,7 +109,7 @@ class Cursor(object):
self.count+=1
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')
@ -117,10 +119,14 @@ 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)
raise
raise
if self.sql_log:
log("query: %s" % self._obj.query)
@ -138,19 +144,20 @@ class Cursor(object):
return res
def print_log(self):
global sql_counter
sql_counter += self.count
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.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.count,sql_counter))
process('from')
process('into')
self.count = 0

89
bin/ssl/cert.cfg Normal file
View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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'):

View File

@ -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
@ -794,7 +806,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)',
@ -843,11 +855,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

66
bin/tools/safe_eval.py Normal file
View File

@ -0,0 +1,66 @@
# -*- encoding: utf-8 -*-
#
# Copyright P. Christeas <p_christ@hol.gr> 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

View File

@ -133,9 +133,12 @@ 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):
return source
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))
res_trans = cr.fetchone()
@ -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

View File

@ -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

View File

@ -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 = []

24
change-loglevel.sh Executable file
View File

@ -0,0 +1,24 @@
#!/bin/bash
ADMIN_PASSWD='admin'
method_1() {
cat '-' << EOF
<xml>
<methodCall>
<methodName>set_loglevel</methodName>
<params>
<param><value><string>$ADMIN_PASSWD</string></value>
</param>
<param>
<value><string>$1</string></value>
</param>
</params>
</methodCall>
EOF
}
LEVEL=10
if [ -n "$1" ] ; then LEVEL=$1 ; fi
method_1 $LEVEL | POST -c 'text/xml' http://localhost:8069/xmlrpc/common
#eof

1067
debian/changelog vendored Normal file

File diff suppressed because it is too large Load Diff

1
debian/compat vendored Normal file
View File

@ -0,0 +1 @@
7

35
debian/control vendored Normal file
View File

@ -0,0 +1,35 @@
Source: openerp-server
Section: net
Priority: optional
Maintainer: Jimmy Angelakos <vyruss@hellug.gr>
Uploaders: Daniel Baumann <daniel@debian.org>
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.

245
debian/copyright vendored Normal file
View File

@ -0,0 +1,245 @@
Author: Tiny.be <info@tiny.be>
Download: http://www.openerp.com/
Files: *
Copyright: (C) 2004-2009 Tiny.be <info@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 <http://www.gnu.org/licenses/>.
.
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 <info@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 <tsarna@sarna.org>
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 <g.rodola@gmail.com>
(C) 2008 Fabien Pinckaers <fp@tinyerp.com>
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 <info@tiny.be>
(C) 2005-2006 Axelor SARL. <http://www.axelor.com/>
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 <info@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 <team@cherrypy.org>
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 <daniel@debian.org>
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 <http://www.gnu.org/licenses/>.
.
On Debian systems, the complete text of the GNU General Public License
can be found in /usr/share/common-licenses/GPL-3 file.

55
debian/local/openerp-server.conf vendored Normal file
View File

@ -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 =

49
debian/openerp-server.README.Debian vendored Normal file
View File

@ -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 = <your 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 = <your ip>'.
-- Daniel Baumann <daniel@debian.org> Fri, 1 Jun 2007 12:00:00 +0200

10
debian/openerp-server.config vendored Normal file
View File

@ -0,0 +1,10 @@
#!/bin/sh
set -e
. /usr/share/debconf/confmodule
db_input low openerp-server/username || true
db_go
db_stop

2
debian/openerp-server.docs vendored Normal file
View File

@ -0,0 +1,2 @@
doc/migrate
doc/tests

1
debian/openerp-server.examples vendored Normal file
View File

@ -0,0 +1 @@
debian/openerp-server.preseed

68
debian/openerp-server.init vendored Normal file
View File

@ -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

1
debian/openerp-server.install vendored Normal file
View File

@ -0,0 +1 @@
debian/local/openerp-server.conf /etc

2
debian/openerp-server.links vendored Normal file
View File

@ -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

View File

@ -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

62
debian/openerp-server.postinst vendored Normal file
View File

@ -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

41
debian/openerp-server.postrm vendored Normal file
View File

@ -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

5
debian/openerp-server.preseed vendored Normal file
View File

@ -0,0 +1,5 @@
################################################################################
## openerp-server
#openerp-server openerp-server/username string openerp
################################################################################

9
debian/openerp-server.templates vendored Normal file
View File

@ -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.

15
debian/patches/01-autobuild.patch vendored Normal file
View File

@ -0,0 +1,15 @@
Author: Daniel Baumann <daniel@debian.org>
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" """

15
debian/patches/02-migrate.patch vendored Normal file
View File

@ -0,0 +1,15 @@
Author: Brian DeRocher <brian@derocher.org>
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='<?xml version="1.0"?><tree string="Menu" toolbar="1"><field icon="icon" name="name"/></tree>' where name='ir.ui.menu.tree' and type='tree' and field_parent='child_id'""",
):

12
debian/patches/03-shebang.patch vendored Normal file
View File

@ -0,0 +1,12 @@
Author: Daniel Baumann <daniel@debian.org>
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
#

0
debian/patches/series vendored Normal file
View File

1
debian/po/POTFILES.in vendored Normal file
View File

@ -0,0 +1 @@
[type: gettext/rfc822deb] openerp-server.templates

39
debian/po/cs.po vendored Normal file
View File

@ -0,0 +1,39 @@
# Czech translation of openerp-server debconf templates.
# Copyright (C) 2009 Vítězslav Kotrla <vitezslav.kotrla@gmail.com>
# 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 <vitezslav.kotrla@gmail.com>\n"
"Language-Team: Czech <debian-l10n-czech@lists.debian.org>\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."

40
debian/po/de.po vendored Normal file
View File

@ -0,0 +1,40 @@
# German translation of openerp-server debconf templates.
# Copyright (C) 2009 Kai Wasserbäch <debian@carbon-project.org>
# Copyright (C) 2009 Helge Kreutzmann <debian@helgefjell.de>
# 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 <debian@helgefjell.de>\n"
"Language-Team: German <debian-l10n-german@lists.debian.org>\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."

41
debian/po/es.po vendored Normal file
View File

@ -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 <fgrequena@gmail.com>
# 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 <fgrequena@gmail.com>\n"
"Language-Team: Spanish <debian-l10n-spanish@lists.debian.org>\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."

41
debian/po/fi.po vendored Normal file
View File

@ -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 <edu@iki.fi>\n"
"Language-Team: Finnish <debian-l10n-finnish@lists.debian.org>\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."

39
debian/po/fr.po vendored Normal file
View File

@ -0,0 +1,39 @@
# French translation of openerp-server debconf templates.
# Copyright (C) 2009 Steve Petruzzello <dlist@bluewin.ch>
# 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 <dlist@bluewin.ch>\n"
"Language-Team: French <debian-l10n-french@lists.debian.org>\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."

41
debian/po/gl.po vendored Normal file
View File

@ -0,0 +1,41 @@
# Galizian translation of openerp-server debconf templates.
# Copyright (C) 2009 Marce Villarino <mvillarino@gmail.com>
# 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 <mvillarino@users.sourceforge.net>\n"
"Language-Team: Galician <proxecto@trasno.ent>\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."

40
debian/po/it.po vendored Normal file
View File

@ -0,0 +1,40 @@
# Italian translation of openerp-server debconf templates.
# Copyright (C) 2009 Vincenzo Campanella <vinz65@gmail.com>
# 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 <vinz65@gmail.com>\n"
"Language-Team: Italian <tp@lists.linux.it>\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."

38
debian/po/ja.po vendored Normal file
View File

@ -0,0 +1,38 @@
# Japanese translation of openerp-server debconf templates.
# Copyright (C) 2009 Hideki Yamane <henrich@debian.or.jp>
# 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) <henrich@debian.or.jp>\n"
"Language-Team: Japanese <debian-japanese@lists.debian.org>\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 "アカウントのユーザ名を選んでください。"

40
debian/po/pt.po vendored Normal file
View File

@ -0,0 +1,40 @@
# Portuguese translation of openerp-server debconf templates.
# Copyright (C) 2009 Américo Monteiro <a_monteiro@netcabo.pt>
# 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 <a_monteiro@netcabo.pt>\n"
"Language-Team: Portuguese <traduz@debianpt.org>\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."

43
debian/po/ru.po vendored Normal file
View File

@ -0,0 +1,43 @@
# German translation of openerp-server debconf templates.
# Copyright (C) 2009 Sergey Alyoshin <alyoshin.s@gmail.com>
# Copyright (C) 2009 Yuri Kozlov <yuray@komyakino.ru>
# 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 <yuray@komyakino.ru>\n"
"Language-Team: Russian <debian-l10n-russian@lists.debian.org>\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 "Укажите имя такой учётной записи."

40
debian/po/sv.po vendored Normal file
View File

@ -0,0 +1,40 @@
# Swedish translation of openerp-server debconf templates.
# Copyright (C) 2009 Martin Bagge <brother@bsnet.se>
# 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 <brother@bsnet.se>\n"
"Language-Team: swedish <debian-l10n-swedish@lists.debian.org>\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."

38
debian/po/templates.pot vendored Normal file
View File

@ -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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

38
debian/po/zh_CN.po vendored Normal file
View File

@ -0,0 +1,38 @@
# Simplified Chinese translation of openerp-server debconf templates.
# Copyright (C) 2009 Deng Xiyue <manphiz-guest@users.alioth.debian.org>
# 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 <manphiz-guest@users.alioth.debian.org>\n"
"Language-Team: Debian Chinese GB <debian-chinese-gb@lists.debian.org>\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 "请选择账户的用户名。"

80
debian/rules vendored Executable file
View File

@ -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

951
doc/Changelog-4.x.moved Normal file
View File

@ -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

36
doc/README.urpmi Normal file
View File

@ -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

31
doc/README.userchange Normal file
View File

@ -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!

27
doc/openerp-server.conf Normal file
View File

@ -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

147
doc/openerp-server.init Normal file
View File

@ -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

View File

@ -0,0 +1,5 @@
/var/log/openerp/*.log {
copytruncate
missingok
notifempty
}

19
get-srvstats.sh Executable file
View File

@ -0,0 +1,19 @@
#!/bin/bash
# ADMIN_PASSWD='admin'
method_1() {
cat '-' << EOF
<xml>
<methodCall>
<methodName>get_stats</methodName>
<params>
</params>
</methodCall>
EOF
}
LEVEL=10
if [ -n "$1" ] ; then LEVEL=$1 ; fi
method_1 $LEVEL | POST -c 'text/xml' http://localhost:8069/xmlrpc/common
#eof

View File

@ -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
# <draft-ietf-http-v10-spec-00.txt> 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 = """\
<head>
<title>Error response</title>
</head>
<body>
<h1>Error response</h1>
<p>Error code %(code)d.
<p>Message: %(message)s.
<p>Error code explanation: %(code)s = %(explain)s.
</body>
"""
DEFAULT_ERROR_CONTENT_TYPE = "text/html"
def _quote_html(html):
return html.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
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
<command> <path> <version>
where <command> is a (case-sensitive) keyword such as GET or POST,
<path> is a string containing path information for the request,
and <version> should be the string "HTTP/1.0" or "HTTP/1.1".
<path> 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
<command> <path>
(i.e. <version> 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
<version> <responsecode> <responsestring>
where <version> is the protocol version ("HTTP/1.0" or "HTTP/1.1"),
<responsecode> is a 3-digit response code indicating success or
failure of the request, and <responsestring> 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 (<command>). 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: <type>/<subtype>
where <type> and <subtype> 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()

View File

@ -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()

View File

@ -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.h>
- 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 <lkcl@samba.org>
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)

View File

@ -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,
)

24
sql/clean-model.sql Normal file
View File

@ -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);