diff --git a/README b/README index e7639e08fed..9449819d766 100644 --- a/README +++ b/README @@ -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 - $ 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 + $ 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: diff --git a/doc/index.rst.inc b/doc/index.rst.inc index 31ac2494a48..d7710067fb8 100644 --- a/doc/index.rst.inc +++ b/doc/index.rst.inc @@ -7,6 +7,7 @@ OpenERP Server import module-versioning + on_change_tips test-framework Changed in 7.0 diff --git a/doc/on_change_tips.rst b/doc/on_change_tips.rst new file mode 100644 index 00000000000..05186d09740 --- /dev/null +++ b/doc/on_change_tips.rst @@ -0,0 +1,51 @@ +.. _on_change_tips: + +On Change Methods +================= + +Definition of on change methods in a view looks like this: + +:: + + + +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). \ No newline at end of file diff --git a/openerp/addons/base/base_data.xml b/openerp/addons/base/base_data.xml index 4c429fe56ff..071d4cd0618 100644 --- a/openerp/addons/base/base_data.xml +++ b/openerp/addons/base/base_data.xml @@ -69,6 +69,7 @@ Administrator + admin@example.com diff --git a/openerp/addons/base/base_demo.xml b/openerp/addons/base/base_demo.xml index ce675ebb99e..cb9c836e0a6 100644 --- a/openerp/addons/base/base_demo.xml +++ b/openerp/addons/base/base_demo.xml @@ -5,6 +5,7 @@ Demo User + demo@example.com @@ -15,6 +16,10 @@ + + admin@example.com + + 1.5289 diff --git a/openerp/addons/base/ir/ir_fields.py b/openerp/addons/base/ir/ir_fields.py index 94a0be9e86c..105176bbd9c 100644 --- a/openerp/addons/base/ir/ir_fields.py +++ b/openerp/addons/base/ir/ir_fields.py @@ -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)) diff --git a/openerp/addons/base/ir/ir_translation.py b/openerp/addons/base/ir/ir_translation.py index 8f05c30e20b..f3985045fb2 100644 --- a/openerp/addons/base/ir/ir_translation.py +++ b/openerp/addons/base/ir/ir_translation.py @@ -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 = [] diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index 9d798d8a636..df42bb81521 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -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): diff --git a/openerp/addons/base/res/res_partner_view.xml b/openerp/addons/base/res/res_partner_view.xml index 92131a5ae46..fef1f2562d4 100644 --- a/openerp/addons/base/res/res_partner_view.xml +++ b/openerp/addons/base/res/res_partner_view.xml @@ -147,7 +147,7 @@ -