[MERGE]merge with trunk
bzr revid: kbh@tinyerp.com-20121109052642-2nte0ax6pu7oktuy
This commit is contained in:
@@ -6,17 +6,9 @@ Customer Relationship Management software. More info at:
|
||||
|
||||
http://www.openerp.com
|
||||
|
||||
Installation on Debian Ubuntu
|
||||
Installation on Debian/Ubuntu
|
||||
-----------------------------
|
||||
|
||||
Download the deb file and type:
|
||||
|
||||
$ sudo dpkg -i <openerp-deb-filename>
|
||||
$ sudo apt-get install install -f
|
||||
|
||||
Installation on Debian Ubuntu from nightly build
|
||||
------------------------------------------------
|
||||
|
||||
Add the the apt repository
|
||||
|
||||
deb http://nightly.openerp.com/6.1/deb/ ./
|
||||
@@ -26,6 +18,11 @@ in your source.list and type:
|
||||
$ sudo apt-get update
|
||||
$ sudo apt-get install openerp
|
||||
|
||||
Or download the deb file and type:
|
||||
|
||||
$ sudo dpkg -i <openerp-deb-filename>
|
||||
$ sudo apt-get install install -f
|
||||
|
||||
Installation on RedHat, Fedora, CentOS
|
||||
--------------------------------------
|
||||
|
||||
@@ -42,6 +39,8 @@ Install the openerp rpm
|
||||
Installation on Windows
|
||||
-----------------------
|
||||
|
||||
Check the notes in setup.py
|
||||
|
||||
Installation on MacOSX
|
||||
-----------------------
|
||||
|
||||
@@ -54,14 +53,7 @@ default master password is "admin".
|
||||
Detailed System Requirements
|
||||
----------------------------
|
||||
|
||||
You need the following software installed:
|
||||
|
||||
postgresql-client, python-dateutil, python-feedparser, python-gdata,
|
||||
python-ldap, python-libxslt1, python-lxml, python-mako, python-openid,
|
||||
python-psycopg2, python-pybabel, python-pychart, python-pydot,
|
||||
python-pyparsing, python-reportlab, python-simplejson, python-tz,
|
||||
python-vatnumber, python-vobject, python-webdav, python-werkzeug, python-xlwt,
|
||||
python-yaml, python-zsi
|
||||
The dependencies are listed in setup.py
|
||||
|
||||
For Luxembourg localization, you also need:
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ OpenERP Server
|
||||
|
||||
import
|
||||
module-versioning
|
||||
on_change_tips
|
||||
test-framework
|
||||
|
||||
Changed in 7.0
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
.. _on_change_tips:
|
||||
|
||||
On Change Methods
|
||||
=================
|
||||
|
||||
Definition of on change methods in a view looks like this:
|
||||
|
||||
::
|
||||
|
||||
<field name="name" on_change="name_change(name, address, city)"/>
|
||||
|
||||
And here is the corresponding method in the model:
|
||||
|
||||
::
|
||||
|
||||
def name_change(self, cr, uid, ids, name, address, city, context=None):
|
||||
...
|
||||
return {
|
||||
'value': {
|
||||
'address': ...
|
||||
'city': ...
|
||||
}
|
||||
}
|
||||
|
||||
On change methods can be confusing when people use them, here are a list of clarifications to avoid any misconception:
|
||||
|
||||
- On change methods can be executed during the creation of a row, long before it is effectively saved into the database.
|
||||
- Fields are *not* validated before going through a on change methods. As an example, a field marqued as required can be False.
|
||||
- On change methods can read data in the database but should *never* attempt to write anything, this is always a strong conception
|
||||
problem.
|
||||
- The format of the values passed to an on change method is exactly the same than the one passed to the write() method. So
|
||||
the on change method must be able to handle any format used for all the fields it process. The following list describe some fields
|
||||
that can have an unusual format.
|
||||
|
||||
- *float*: Due to the way JSON represents numbers and the way the JSON library of Python handles it, a float field will not always
|
||||
be represented as a python float type. When the number can be represented as an integer it will appear as a python integer type.
|
||||
This can be a problem when using some mathematical operations (example: price / 2), so it is a good practice to always cast any number
|
||||
to float when you want to handle floats in on change methods.
|
||||
- *one2many and many2many*: There are plenty of misconception about x2many fields in on change methods. The reality is, in fact, quite
|
||||
complex. x2many are defined by a list of operations, each operation was given a number (0 -> create, 1 -> write, ect...) and has
|
||||
its own semantic. To be able to use one2many and many2many in on change methods, you are strongly encourage to use the
|
||||
resolve_2many_commands() method. Here is a sample usage:
|
||||
|
||||
::
|
||||
|
||||
values = self.resolve_2many_commands(cr, uid, 'my_o2m', my_o2m_values, ['price', 'tax'], context)
|
||||
|
||||
This code will convert the complex list of operations that makes the o2m value into a simple list of dictionaries containing the fields
|
||||
'price' and 'tax', which is way simpler to handle in most on change methods. Please note that you can also return a list of
|
||||
dictionaries as the new value of a one2many, it will replace the actual rows contained in that one2many (but it will also remove the
|
||||
previous ones).
|
||||
@@ -69,6 +69,7 @@
|
||||
<field name="name">Administrator</field>
|
||||
<field name="company_id" ref="main_company"/>
|
||||
<field name="customer" eval="False"/>
|
||||
<field name="email">admin@example.com</field>
|
||||
</record>
|
||||
|
||||
<record model="res.users" id="base.user_root">
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<field name="name">Demo User</field>
|
||||
<field name="company_id" ref="main_company"/>
|
||||
<field name="customer" eval="False"/>
|
||||
<field name="email">demo@example.com</field>
|
||||
</record>
|
||||
<record id="user_demo" model="res.users">
|
||||
<field name="partner_id" ref="base.partner_demo"/>
|
||||
@@ -15,6 +16,10 @@
|
||||
<field name="groups_id" eval="[(6,0,[ref('base.group_user')])]"/>
|
||||
</record>
|
||||
|
||||
<record model="res.partner" id="base.partner_root">
|
||||
<field name="email">admin@example.com</field>
|
||||
</record>
|
||||
|
||||
<!-- new rate for demo transactions in multi currency -->
|
||||
<record id="rateUSDbis" model="res.currency.rate">
|
||||
<field name="rate">1.5289</field>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import collections
|
||||
import datetime
|
||||
import functools
|
||||
import operator
|
||||
@@ -31,9 +32,70 @@ REPLACE_WITH = lambda ids: (6, False, ids)
|
||||
|
||||
class ConversionNotFound(ValueError): pass
|
||||
|
||||
class ColumnWrapper(object):
|
||||
def __init__(self, column, cr, uid, pool, fromtype, context=None):
|
||||
self._converter = None
|
||||
self._column = column
|
||||
if column._obj:
|
||||
self._pool = pool
|
||||
self._converter_args = {
|
||||
'cr': cr,
|
||||
'uid': uid,
|
||||
'model': pool[column._obj],
|
||||
'fromtype': fromtype,
|
||||
'context': context
|
||||
}
|
||||
@property
|
||||
def converter(self):
|
||||
if not self._converter:
|
||||
self._converter = self._pool['ir.fields.converter'].for_model(
|
||||
**self._converter_args)
|
||||
return self._converter
|
||||
|
||||
def __getattr__(self, item):
|
||||
return getattr(self._column, item)
|
||||
|
||||
class ir_fields_converter(orm.Model):
|
||||
_name = 'ir.fields.converter'
|
||||
|
||||
def for_model(self, cr, uid, model, fromtype=str, context=None):
|
||||
""" Returns a converter object for the model. A converter is a
|
||||
callable taking a record-ish (a dictionary representing an openerp
|
||||
record with values of typetag ``fromtype``) and returning a converted
|
||||
records matching what :meth:`openerp.osv.orm.Model.write` expects.
|
||||
|
||||
:param model: :class:`openerp.osv.orm.Model` for the conversion base
|
||||
:returns: a converter callable
|
||||
:rtype: (record: dict, logger: (field, error) -> None) -> dict
|
||||
"""
|
||||
columns = dict(
|
||||
(k, ColumnWrapper(v.column, cr, uid, self.pool, fromtype, context))
|
||||
for k, v in model._all_columns.iteritems())
|
||||
converters = dict(
|
||||
(k, self.to_field(cr, uid, model, column, fromtype, context))
|
||||
for k, column in columns.iteritems())
|
||||
|
||||
def fn(record, log):
|
||||
converted = {}
|
||||
for field, value in record.iteritems():
|
||||
if field in (None, 'id', '.id'): continue
|
||||
if not value:
|
||||
converted[field] = False
|
||||
continue
|
||||
try:
|
||||
converted[field], ws = converters[field](value)
|
||||
for w in ws:
|
||||
if isinstance(w, basestring):
|
||||
# wrap warning string in an ImportWarning for
|
||||
# uniform handling
|
||||
w = ImportWarning(w)
|
||||
log(field, w)
|
||||
except ValueError, e:
|
||||
log(field, e)
|
||||
|
||||
return converted
|
||||
return fn
|
||||
|
||||
def to_field(self, cr, uid, model, column, fromtype=str, context=None):
|
||||
""" Fetches a converter for the provided column object, from the
|
||||
specified type.
|
||||
@@ -343,6 +405,10 @@ class ir_fields_converter(orm.Model):
|
||||
# [{subfield:ref1},{subfield:ref2},{subfield:ref3}]
|
||||
records = ({subfield:item} for item in record[subfield].split(','))
|
||||
|
||||
def log(_, e):
|
||||
if not isinstance(e, Warning):
|
||||
raise e
|
||||
warnings.append(e)
|
||||
for record in records:
|
||||
id = None
|
||||
refs = only_ref_fields(record)
|
||||
@@ -355,7 +421,7 @@ class ir_fields_converter(orm.Model):
|
||||
cr, uid, model, column, subfield, reference, context=context)
|
||||
warnings.extend(w2)
|
||||
|
||||
writable = exclude_ref_fields(record)
|
||||
writable = column.converter(exclude_ref_fields(record), log)
|
||||
if id:
|
||||
commands.append(LINK_TO(id))
|
||||
commands.append(UPDATE(id, writable))
|
||||
|
||||
@@ -24,6 +24,7 @@ import logging
|
||||
|
||||
import openerp.modules
|
||||
from openerp.osv import fields, osv
|
||||
from tools.translate import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -336,6 +337,8 @@ class ir_translation(osv.osv):
|
||||
trans_model = self.pool.get(model)
|
||||
domain = ['&', ('res_id', '=', id), ('name', '=like', model + ',%')]
|
||||
langs_ids = self.pool.get('res.lang').search(cr, uid, [('code', '!=', 'en_US')], context=context)
|
||||
if not langs_ids:
|
||||
raise osv.except_osv(_('Error'), _("Translation features are unavailable until you install an extra OpenERP translation."))
|
||||
langs = [lg.code for lg in self.pool.get('res.lang').browse(cr, uid, langs_ids, context=context)]
|
||||
main_lang = 'en_US'
|
||||
translatable_fields = []
|
||||
|
||||
@@ -264,10 +264,15 @@ class res_partner(osv.osv, format_address):
|
||||
return False
|
||||
|
||||
def _get_default_image(self, cr, uid, is_company, context=None, colorize=False):
|
||||
if is_company:
|
||||
image = open(openerp.modules.get_module_resource('base', 'static/src/img', 'company_image.png')).read()
|
||||
else:
|
||||
image = tools.image_colorize(open(openerp.modules.get_module_resource('base', 'static/src/img', 'avatar.png')).read())
|
||||
img_path = openerp.modules.get_module_resource('base', 'static/src/img',
|
||||
('company_image.png' if is_company else 'avatar.png'))
|
||||
with open(img_path, 'rb') as f:
|
||||
image = f.read()
|
||||
|
||||
# colorize user avatars
|
||||
if not is_company:
|
||||
image = tools.image_colorize(image)
|
||||
|
||||
return tools.image_resize_image_big(image.encode('base64'))
|
||||
|
||||
def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
|
||||
|
||||
@@ -147,7 +147,7 @@
|
||||
|
||||
<group>
|
||||
<group>
|
||||
<label for="type" attrs="{'invisible': [('parent_id','=', False)]}" invisible="1"/>
|
||||
<label for="type" attrs="{'invisible': [('parent_id','=', False)]}"/>
|
||||
<div attrs="{'invisible': [('parent_id','=', False)]}" invisible="1" name="div_type">
|
||||
<field class="oe_inline"
|
||||
name="type"/>
|
||||
|
||||
@@ -202,6 +202,7 @@
|
||||
<field name="name" readonly="1" class="oe_inline"/>
|
||||
(<field name="login" readonly="1" class="oe_inline"/>)
|
||||
</h1>
|
||||
<button name="preference_change_password" type="object" string="Change password" class="oe_link"/>
|
||||
<group name="preferences" col="4">
|
||||
<field name="lang" readonly="0"/>
|
||||
<field name="tz" readonly="0"/>
|
||||
@@ -213,7 +214,6 @@
|
||||
<field name="signature" readonly="0"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="preference_change_password" type="object" string="Change password"/>
|
||||
<button name="preference_save" type="object" string="Save"/>
|
||||
or
|
||||
<button name="preference_cancel" string="Cancel" special="cancel" class="oe_link"/>
|
||||
|
||||
@@ -38,7 +38,6 @@ import openerp.osv as osv
|
||||
import openerp.pooler as pooler
|
||||
import openerp.release as release
|
||||
import openerp.tools as tools
|
||||
import openerp.tools.assertion_report as assertion_report
|
||||
from openerp import SUPERUSER_ID
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
@@ -285,7 +284,7 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
|
||||
|
||||
# processed_modules: for cleanup step after install
|
||||
# loaded_modules: to avoid double loading
|
||||
report = assertion_report.assertion_report()
|
||||
report = pool._assertion_report
|
||||
loaded_modules, processed_modules = load_module_graph(cr, graph, status, perform_checks=(not update_module), report=report)
|
||||
|
||||
if tools.config['load_language']:
|
||||
|
||||
@@ -32,6 +32,7 @@ import openerp.cron
|
||||
import openerp.tools
|
||||
import openerp.modules.db
|
||||
import openerp.tools.config
|
||||
from openerp.tools import assertion_report
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -49,6 +50,7 @@ class Registry(object):
|
||||
self._store_function = {}
|
||||
self._init = True
|
||||
self._init_parent = {}
|
||||
self._assertion_report = assertion_report.assertion_report()
|
||||
|
||||
# modules fully loaded (maintained during init phase by `loading` module)
|
||||
self._init_modules = set()
|
||||
|
||||
+14
-29
@@ -1352,9 +1352,11 @@ class BaseModel(object):
|
||||
noupdate=noupdate, res_id=id, context=context))
|
||||
cr.execute('RELEASE SAVEPOINT model_load_save')
|
||||
except psycopg2.Warning, e:
|
||||
_logger.exception('Failed to import record %s', record)
|
||||
cr.execute('ROLLBACK TO SAVEPOINT model_load_save')
|
||||
messages.append(dict(info, type='warning', message=str(e)))
|
||||
except psycopg2.Error, e:
|
||||
_logger.exception('Failed to import record %s', record)
|
||||
# Failed to write, log to messages, rollback savepoint (to
|
||||
# avoid broken transaction) and keep going
|
||||
cr.execute('ROLLBACK TO SAVEPOINT model_load_save')
|
||||
@@ -1458,15 +1460,16 @@ class BaseModel(object):
|
||||
field_names = dict(
|
||||
(f, (Translation._get_source(cr, uid, self._name + ',' + f, 'field',
|
||||
context.get('lang'))
|
||||
or column.string or f))
|
||||
or column.string))
|
||||
for f, column in columns.iteritems())
|
||||
converters = dict(
|
||||
(k, Converter.to_field(cr, uid, self, column, context=context))
|
||||
for k, column in columns.iteritems())
|
||||
|
||||
convert = Converter.for_model(cr, uid, self, context=context)
|
||||
|
||||
def _log(base, field, exception):
|
||||
type = 'warning' if isinstance(exception, Warning) else 'error'
|
||||
record = dict(base, field=field, type=type,
|
||||
# logs the logical (not human-readable) field name for automated
|
||||
# processing of response, but injects human readable in message
|
||||
record = dict(base, type=type, field=field,
|
||||
message=unicode(exception.args[0]) % base)
|
||||
if len(exception.args) > 1 and exception.args[1]:
|
||||
record.update(exception.args[1])
|
||||
@@ -1476,7 +1479,6 @@ class BaseModel(object):
|
||||
for record, extras in stream:
|
||||
dbid = False
|
||||
xid = False
|
||||
converted = {}
|
||||
# name_get/name_create
|
||||
if None in record: pass
|
||||
# xid
|
||||
@@ -1497,27 +1499,8 @@ class BaseModel(object):
|
||||
message=_(u"Unknown database identifier '%s'") % dbid))
|
||||
dbid = False
|
||||
|
||||
for field, strvalue in record.iteritems():
|
||||
if field in (None, 'id', '.id'): continue
|
||||
if not strvalue:
|
||||
converted[field] = False
|
||||
continue
|
||||
|
||||
# In warnings and error messages, use translated string as
|
||||
# field name
|
||||
message_base = dict(
|
||||
extras, record=stream.index, field=field_names[field])
|
||||
try:
|
||||
converted[field], ws = converters[field](strvalue)
|
||||
|
||||
for w in ws:
|
||||
if isinstance(w, basestring):
|
||||
# wrap warning string in an ImportWarning for
|
||||
# uniform handling
|
||||
w = ImportWarning(w)
|
||||
_log(message_base, field, w)
|
||||
except ValueError, e:
|
||||
_log(message_base, field, e)
|
||||
converted = convert(record, lambda field, err:\
|
||||
_log(dict(extras, record=stream.index, field=field_names[field]), field, err))
|
||||
|
||||
yield dbid, xid, converted, dict(extras, record=stream.index)
|
||||
|
||||
@@ -3878,10 +3861,12 @@ class BaseModel(object):
|
||||
getattr(wf_service, trigger)(uid, self._name, res_id, cr)
|
||||
|
||||
def _workflow_signal(self, cr, uid, ids, signal, context=None):
|
||||
"""Send given workflow signal"""
|
||||
"""Send given workflow signal and return a dict mapping ids to workflow results"""
|
||||
wf_service = netsvc.LocalService("workflow")
|
||||
result = {}
|
||||
for res_id in ids:
|
||||
wf_service.trg_validate(uid, self._name, res_id, signal, cr)
|
||||
result[res_id] = wf_service.trg_validate(uid, self._name, res_id, signal, cr)
|
||||
return result
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
|
||||
+2
-1
@@ -188,7 +188,8 @@ class object_proxy(object):
|
||||
object = pooler.get_pool(cr.dbname).get(obj)
|
||||
if not object:
|
||||
raise except_osv('Object Error', 'Object %s doesn\'t exist' % str(obj))
|
||||
return object._workflow_signal(cr, uid, [args[0]], signal)
|
||||
res_id = args[0]
|
||||
return object._workflow_signal(cr, uid, [res_id], signal)[res_id]
|
||||
|
||||
@check
|
||||
def exec_workflow(self, db, uid, obj, signal, *args):
|
||||
|
||||
@@ -34,7 +34,6 @@ import openerp.netsvc
|
||||
import openerp.osv
|
||||
import openerp.tools
|
||||
import openerp.service.wsgi_server
|
||||
import openerp.service.workers
|
||||
|
||||
#.apidoc title: RPC Services
|
||||
|
||||
@@ -119,6 +118,7 @@ def stop_services():
|
||||
openerp.modules.registry.RegistryManager.delete_all()
|
||||
|
||||
def start_services_workers():
|
||||
import openerp.service.workers
|
||||
openerp.multi_process = True # Nah!
|
||||
|
||||
openerp.service.workers.Multicorn(openerp.service.wsgi_server.application).run()
|
||||
|
||||
@@ -3,20 +3,17 @@
|
||||
# TODO rename class: Multicorn -> Arbiter ?
|
||||
#-----------------------------------------------------------
|
||||
import errno
|
||||
try:
|
||||
import fcntl
|
||||
except ImportError:
|
||||
fcntl = None
|
||||
import fcntl
|
||||
import logging
|
||||
import os
|
||||
import psutil
|
||||
import random
|
||||
import resource
|
||||
import select
|
||||
import socket
|
||||
import time
|
||||
import logging
|
||||
import os
|
||||
import signal
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
|
||||
import werkzeug.serving
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ class One2ManyMultiple(orm.Model):
|
||||
_name = 'export.one2many.multiple'
|
||||
|
||||
_columns = {
|
||||
'parent_id': fields.many2one('export.one2many.recursive'),
|
||||
'const': fields.integer(),
|
||||
'child1': fields.one2many('export.one2many.child.1', 'parent_id'),
|
||||
'child2': fields.one2many('export.one2many.child.2', 'parent_id'),
|
||||
@@ -135,3 +136,11 @@ class SelectionWithDefault(orm.Model):
|
||||
'const': 4,
|
||||
'value': 2,
|
||||
}
|
||||
|
||||
class RecO2M(orm.Model):
|
||||
_name = 'export.one2many.recursive'
|
||||
|
||||
_columns = {
|
||||
'value': fields.integer(),
|
||||
'child': fields.one2many('export.one2many.multiple', 'parent_id')
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
[["Wood y Wood Pecker", "", "Snow Street, 25", "Kainuu", "Finland", "Supplier", "1", "0", "1", ""], ["Roger Pecker", "Default", "Snow Street, 27", "Kainuu", "Finland", "Supplier", "1", "0", "0", "Wood y Wood Pecker"], ["Sharon Pecker", "Shipping", "Snow Street, 28", "Kainuu", "Finland", "Supplier", "1", "0", "0", "Wood y Wood Pecker"], ["Thomas Pecker", "Contact", "Snow Street, 27", "Kainuu", "Finland", "Supplier", "1", "0", "0", "Wood y Wood Pecker"], ["Norseman Roundabout", "", "Atonium Street, 45a", "Brussels", "Belgium", "Supplier", "1", "0", "1", ""], ["Yvan Holiday", "Invoice", "Atonium Street, 45b", "Brussels", "Belgium", "Supplier", "1", "0", "0", "Norseman Roundabout"], ["Jack Unsworth", "Contact", "Atonium Street, 45a", "Brussels", "Belgium", "Supplier", "1", "0", "0", "Norseman Roundabout"]]
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
import pkgutil
|
||||
import unittest2
|
||||
|
||||
import openerp.modules.registry
|
||||
import openerp
|
||||
@@ -246,7 +247,7 @@ class test_integer_field(ImporterCase):
|
||||
-1, -42, -(2**31 - 1), -(2**31), -12345678
|
||||
], values(self.read()))
|
||||
|
||||
@mute_logger('openerp.sql_db')
|
||||
@mute_logger('openerp.sql_db', 'openerp.osv.orm')
|
||||
def test_out_of_range(self):
|
||||
result = self.import_(['value'], [[str(2**31)]])
|
||||
self.assertIs(result['ids'], False)
|
||||
@@ -388,14 +389,14 @@ class test_unbound_string_field(ImporterCase):
|
||||
class test_required_string_field(ImporterCase):
|
||||
model_name = 'export.string.required'
|
||||
|
||||
@mute_logger('openerp.sql_db')
|
||||
@mute_logger('openerp.sql_db', 'openerp.osv.orm')
|
||||
def test_empty(self):
|
||||
result = self.import_(['value'], [[]])
|
||||
self.assertEqual(result['messages'], [message(
|
||||
u"Missing required value for the field 'unknown'")])
|
||||
self.assertIs(result['ids'], False)
|
||||
|
||||
@mute_logger('openerp.sql_db')
|
||||
@mute_logger('openerp.sql_db', 'openerp.osv.orm')
|
||||
def test_not_provided(self):
|
||||
result = self.import_(['const'], [['12']])
|
||||
self.assertEqual(result['messages'], [message(
|
||||
@@ -1007,6 +1008,46 @@ class test_realworld(common.TransactionCase):
|
||||
self.assertFalse(result['messages'])
|
||||
self.assertEqual(len(result['ids']), len(data))
|
||||
|
||||
def test_backlink(self):
|
||||
data = json.loads(pkgutil.get_data(self.__module__, 'contacts.json'))
|
||||
result = self.registry('res.partner').load(
|
||||
self.cr, openerp.SUPERUSER_ID,
|
||||
["name", "type", "street", "city", "country_id", "category_id",
|
||||
"supplier", "customer", "is_company", "parent_id"],
|
||||
data)
|
||||
self.assertFalse(result['messages'])
|
||||
self.assertEqual(len(result['ids']), len(data))
|
||||
|
||||
def test_recursive_o2m(self):
|
||||
""" The content of the o2m field's dict needs to go through conversion
|
||||
as it may be composed of convertables or other relational fields
|
||||
"""
|
||||
self.registry('ir.model.data').clear_caches()
|
||||
Model = self.registry('export.one2many.recursive')
|
||||
result = Model.load(self.cr, openerp.SUPERUSER_ID,
|
||||
['value', 'child/const', 'child/child1/str', 'child/child2/value'],
|
||||
[
|
||||
['4', '42', 'foo', '55'],
|
||||
['', '43', 'bar', '56'],
|
||||
['', '', 'baz', ''],
|
||||
['', '55', 'qux', '57'],
|
||||
['5', '99', 'wheee', ''],
|
||||
['', '98', '', '12'],
|
||||
],
|
||||
context=None)
|
||||
|
||||
self.assertFalse(result['messages'])
|
||||
self.assertEqual(len(result['ids']), 2)
|
||||
|
||||
b = Model.browse(self.cr, openerp.SUPERUSER_ID, result['ids'], context=None)
|
||||
self.assertEqual((b[0].value, b[1].value), (4, 5))
|
||||
|
||||
self.assertEqual([child.str for child in b[0].child[1].child1],
|
||||
['bar', 'baz'])
|
||||
self.assertFalse(len(b[1].child[1].child1))
|
||||
self.assertEqual([child.value for child in b[1].child[1].child2],
|
||||
[12])
|
||||
|
||||
class test_date(ImporterCase):
|
||||
model_name = 'export.date'
|
||||
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import glob, os, re, setuptools, sys
|
||||
from os.path import join, isfile
|
||||
|
||||
# List all data files
|
||||
def data():
|
||||
files = []
|
||||
for root, dirnames, filenames in os.walk('openerp'):
|
||||
for filename in filenames:
|
||||
if not re.match(r'.*(\.pyc|\.pyo|\~)$',filename):
|
||||
files.append(os.path.join(root, filename))
|
||||
d = {}
|
||||
for v in files:
|
||||
k=os.path.dirname(v)
|
||||
if k in d:
|
||||
d[k].append(v)
|
||||
else:
|
||||
d[k]=[v]
|
||||
r = d.items()
|
||||
if os.name == 'nt':
|
||||
r.append(("Microsoft.VC90.CRT", glob.glob('C:\Microsoft.VC90.CRT\*.*')))
|
||||
|
||||
import babel
|
||||
r.append(("localedata",
|
||||
glob.glob(os.path.join(os.path.dirname(babel.__file__), "localedata" , '*'))))
|
||||
|
||||
return r
|
||||
|
||||
def gen_manifest():
|
||||
file_list="\n".join(data())
|
||||
open('MANIFEST','w').write(file_list)
|
||||
|
||||
if os.name == 'nt':
|
||||
sys.path.append("C:\Microsoft.VC90.CRT")
|
||||
|
||||
def py2exe_options():
|
||||
if os.name == 'nt':
|
||||
import py2exe
|
||||
return {
|
||||
"console" : [ { "script": "openerp-server", "icon_resources": [(1, join("install","openerp-icon.ico"))], }],
|
||||
'options' : {
|
||||
"py2exe": {
|
||||
"skip_archive": 1,
|
||||
"optimize": 2,
|
||||
"dist_dir": 'dist',
|
||||
"packages": [ "DAV", "HTMLParser", "PIL", "asynchat", "asyncore", "commands", "dateutil", "decimal", "email", "encodings", "imaplib", "lxml", "lxml._elementpath", "lxml.builder", "lxml.etree", "lxml.objectify", "mako", "openerp", "poplib", "pychart", "pydot", "pyparsing", "reportlab", "select", "simplejson", "smtplib", "uuid", "vatnumber", "vobject", "xml", "xml.dom", "yaml", ],
|
||||
"excludes" : ["Tkconstants","Tkinter","tcl"],
|
||||
}
|
||||
}
|
||||
}
|
||||
else:
|
||||
return {}
|
||||
|
||||
execfile(join(os.path.dirname(__file__), 'openerp', 'release.py'))
|
||||
|
||||
# Notes for OpenERP developer on windows:
|
||||
#
|
||||
# To setup a windows developer evironement install python2.7 then pip and use
|
||||
# "pip install <depencey>" for every dependency listed below.
|
||||
#
|
||||
# Dependecies that requires DLLs are not installable with pip install, for
|
||||
# them we added comments with links where you can find the installers.
|
||||
#
|
||||
# OpenERP on windows also require the pywin32, the binary can be found at
|
||||
# http://pywin32.sf.net
|
||||
#
|
||||
# Both python2.7 32bits and 64bits are known to work.
|
||||
|
||||
setuptools.setup(
|
||||
name = 'openerp',
|
||||
version = version,
|
||||
description = description,
|
||||
long_description = long_desc,
|
||||
url = url,
|
||||
author = author,
|
||||
author_email = author_email,
|
||||
classifiers = filter(None, classifiers.split("\n")),
|
||||
license = license,
|
||||
scripts = ['openerp-server'],
|
||||
data_files = data(),
|
||||
packages = setuptools.find_packages(),
|
||||
dependency_links = ['http://download.gna.org/pychart/'],
|
||||
#include_package_data = True,
|
||||
install_requires = [
|
||||
'pychart', # not on pypi, use: pip install http://download.gna.org/pychart/PyChart-1.39.tar.gz
|
||||
'babel',
|
||||
'docutils',
|
||||
'feedparser',
|
||||
'gdata',
|
||||
'lxml < 3', # windows binary http://www.lfd.uci.edu/~gohlke/pythonlibs/
|
||||
'mako',
|
||||
'PIL', # windows binary http://www.lfd.uci.edu/~gohlke/pythonlibs/
|
||||
'psutil', # windows binary code.google.com/p/psutil/downloads/list
|
||||
'psycopg2',
|
||||
'pydot',
|
||||
'python-dateutil < 2',
|
||||
'python-ldap', # optional
|
||||
'python-openid',
|
||||
'pytz',
|
||||
'pywebdav',
|
||||
'pyyaml',
|
||||
'reportlab', # windows binary pypi.python.org/pypi/reportlab
|
||||
'simplejson',
|
||||
'vatnumber',
|
||||
'vobject',
|
||||
'werkzeug',
|
||||
'xlwt',
|
||||
],
|
||||
extras_require = {
|
||||
'SSL' : ['pyopenssl'],
|
||||
},
|
||||
tests_require = ['unittest2'],
|
||||
**py2exe_options()
|
||||
)
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
Reference in New Issue
Block a user