[MERGE] Upstream

bzr revid: stw@openerp.com-20111005142057-qshud5cor3s7j8v7
This commit is contained in:
Stephane Wirtel 2011-10-05 16:20:57 +02:00
commit 7fdabc5b66
24 changed files with 921 additions and 531 deletions

View File

@ -1,12 +1,13 @@
graft debian
graft doc
graft install
graft openerp
graft tests
graft win32
include README
include LICENSE
include MANIFEST.in
include setup.nsi
include setup.cfg
include setup_rpm.sh
recursive-include win32 *.py *.bat
recursive-include openerp *css *csv *html *png *po *pot *rml *rng *sql *sxw *xml *xsl *yml
graft install
graft debian
graft doc
include gunicorn.conf.py
include openerp-server
include setup*
global-exclude *pyc *~ # Exclude possible garbage from previous graft.

View File

@ -12,9 +12,9 @@ case "${1}" in
chown openerp:openerp /etc/openerp/openerp-server.conf
chmod 0640 /etc/openerp/openerp-server.conf
# Creating log file
touch /var/log/openerp.log
chown openerp:openerp /var/log/openerp.log
chmod 0640 /var/log/openerp.log
touch /var/log/openerp-server.log
chown openerp:openerp /var/log/openerp-server.log
chmod 0640 /var/log/openerp-server.log
# Creating local storage directory
mkdir -p /var/lib/openerp/filestore
chown openerp:openerp -R /var/lib/openerp

15
debian/po/es_CL.po vendored
View File

@ -8,20 +8,20 @@ msgstr ""
"Project-Id-Version: openobject-server\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2009-08-24 22:41+0300\n"
"PO-Revision-Date: 2011-01-19 14:33+0000\n"
"Last-Translator: Juan Pizarro <jpizarrom@gmail.com>\n"
"PO-Revision-Date: 2011-10-03 16:05+0000\n"
"Last-Translator: doingit.cl <Unknown>\n"
"Language-Team: Spanish (Chile) <es_CL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2011-09-01 04:46+0000\n"
"X-Generator: Launchpad (build 13827)\n"
"X-Launchpad-Export-Date: 2011-10-04 05:01+0000\n"
"X-Generator: Launchpad (build 14071)\n"
#. Type: string
#. Description
#: ../openerp-server.templates:1001
msgid "Dedicated system account for the Open ERP server:"
msgstr ""
msgstr "Cuenta del sistema dedicada para el servidor OpenERP"
#. Type: string
#. Description
@ -31,9 +31,12 @@ msgid ""
"the system's security is not compromised by running it with superuser "
"privileges."
msgstr ""
"El servidor OpenERP 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 ""
msgstr "Elija un Nombre de Usuario para la cuenta"

View File

@ -24,8 +24,7 @@
{
'name': 'Base',
'version': '1.3',
'category': 'Generic Modules/Base',
'complexity': "easy",
'category': 'System',
'description': """The kernel of OpenERP, needed for all installation.""",
'author': 'OpenERP SA',
'maintainer': 'OpenERP SA',

View File

@ -1,6 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<record id="parameter_web_base_url" model="ir.config_parameter">
<field name="key">web.base.url</field>
<field name="value">http://localhost:8069</field>
</record>
<record id="view_menu" model="ir.ui.view">
<field name="name">ir.ui.menu.tree</field>
<field name="model">ir.ui.menu</field>

View File

@ -7,13 +7,13 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2011-01-11 11:14+0000\n"
"PO-Revision-Date: 2011-07-26 01:09+0000\n"
"Last-Translator: Juano <Unknown>\n"
"PO-Revision-Date: 2011-10-03 16:01+0000\n"
"Last-Translator: Francisco Reyes Acuña <Unknown>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2011-10-01 05:07+0000\n"
"X-Launchpad-Export-Date: 2011-10-04 05:01+0000\n"
"X-Generator: Launchpad (build 14071)\n"
#. module: base
@ -5726,7 +5726,7 @@ msgstr "Traducciones"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number Padding"
msgstr ""
msgstr "Número(s) de relleno"
#. module: base
#: view:ir.actions.report.xml:0

File diff suppressed because it is too large Load Diff

View File

@ -7,44 +7,50 @@
<field name="name">ir.values.form.action</field>
<field name="model">ir.values</field>
<field name="type">form</field>
<field name="priority">20</field>
<field name="arch" type="xml">
<form string="Connect Events to Actions">
<field name="name" required="1"/>
<form string="Action Bindings">
<field name="name"/>
<newline/>
<group col="2" colspan="2">
<separator string="Action Source" colspan="2"/>
<separator string="Action Binding" colspan="2"/>
<field name="model_id" on_change="onchange_object_id(model_id)"/>
<field name="model"/>
<field name="res_id"/>
<field name="key2" required="1"/>
<field name="key2"/>
</group>
<group col="2" colspan="2">
<separator string="Action To Launch" colspan="2"/>
<separator string="Action" colspan="2"/>
<field name="action_id" on_change="onchange_action_id(action_id)"/>
<field name="value_unpickle" colspan="4" string="Action Reference"/>
</group>
</form>
</field>
</record>
<field name="object" readonly="1"/>
<record id="values_view_form_defaults" model="ir.ui.view">
<field name="name">ir.values.form.defaults</field>
<field name="model">ir.values</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="User-defined Defaults">
<field name="name"/>
<newline/>
<group col="2" colspan="2">
<separator string="Model" colspan="2"/>
<field name="model_id" on_change="onchange_object_id(model_id)"/>
<field name="model"/>
<field name="key2" string="Condition"/>
</group>
<group col="2" colspan="2">
<separator string="Values for Event Type" colspan="2"/>
<label string="client_action_multi, client_action_relate" colspan="2"/>
<label string="tree_but_open, client_print_multi" colspan="2"/>
<separator string="Default Value" colspan="2"/>
<field name="value_unpickle" colspan="4" nolabel="1"/>
</group>
<group col="2" colspan="2">
<separator colspan="2" string="Value"/>
<field name="value_unpickle" nolabel="1" colspan="4"/>
<separator colspan="2" string="Default Value Scope"/>
<field name="user_id"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<group col="2" colspan="2">
<separator colspan="2" string="Metadata"/>
<field name="meta_unpickle" nolabel="1"/>
</group>
<group col="2" colspan="2">
<separator colspan="2" string=""/>
<field name="user_id"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
</form>
</form>
</field>
</record>
@ -53,10 +59,9 @@
<field name="model">ir.values</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Client Actions">
<tree string="Action Bindings/Defaults">
<field name="name"/>
<field name="model"/>
<field name="action_id"/>
<field name="key2"/>
</tree>
</field>
@ -73,7 +78,7 @@
<field name="key2"/>
<newline/>
<group expand="0" string="Group By...">
<filter string="Object" icon="terp-stock_align_left_24" domain="[]" context="{'group_by':'model'}"/>
<filter string="Model" icon="terp-stock_align_left_24" domain="[]" context="{'group_by':'model'}"/>
<filter string="Type" icon="terp-stock_symbol-selection" domain="[]" context="{'group_by':'key2'}"/>
</group>
</search>
@ -81,23 +86,21 @@
</record>
<record id="act_values_form_action" model="ir.actions.act_window">
<field name="name">Client Events</field>
<field name="name">Action Bindings</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">ir.values</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="values_view_search_action"/>
<field name="domain">[('key','=','action')]</field>
<field name="context">{'read':'default','default_object':1}</field>
<field name="context">{'default_key':'action'}</field>
</record>
<record model="ir.actions.act_window.view" id="action_values_tree_view">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="values_view_tree_action"/>
<field name="act_window_id" ref="act_values_form_action"/>
</record>
<record model="ir.actions.act_window.view" id="action_values_form_view">
<field name="sequence" eval="2"/>
<field name="view_mode">form</field>
@ -105,54 +108,27 @@
<field name="act_window_id" ref="act_values_form_action"/>
</record>
<!-- Values -->
<record id="values_view_form" model="ir.ui.view">
<field name="name">ir.values.form</field>
<field name="model">ir.values</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Values">
<field name="name" select="1"/>
<field name="model" select="1"/>
<field name="key" select="1"/>
<field name="key2" select="2"/>
<field name="object" select="2"/>
<field name="res_id"/>
<field name="user_id" select="2"/>
<field name="company_id" select="2"/>
<field name="value_unpickle"/>
<field name="meta_unpickle"/>
</form>
</field>
</record>
<record id="values_view_tree" model="ir.ui.view">
<field name="name">ir.values.tree</field>
<field name="model">ir.values</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Values">
<field name="name"/>
<field name="model"/>
<field name="key"/>
<field name="key2"/>
<field name="user_id"/>
<field name="company_id"/>
</tree>
</field>
</record>
<record id="act_values_form" model="ir.actions.act_window">
<field name="name">Client Actions Connections</field>
<record id="act_values_form_defaults" model="ir.actions.act_window">
<field name="name">User-defined Defaults</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">ir.values</field>
<field name="view_type">form</field>
<field name="view_id" ref="values_view_tree"/>
<field name="context">{'read':'default'}</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="values_view_search_action"/>
<field name="domain">[('key','=','default')]</field>
<field name="context">{'default_key':'default','default_key2':''}</field>
</record>
<record model="ir.actions.act_window.view" id="action_values_defaults_tree_view">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="values_view_tree_action"/>
<field name="act_window_id" ref="act_values_form_defaults"/>
</record>
<record model="ir.actions.act_window.view" id="action_values_defaults_form_view">
<field name="sequence" eval="2"/>
<field name="view_mode">form</field>
<field name="view_id" ref="values_view_form_defaults"/>
<field name="act_window_id" ref="act_values_form_defaults"/>
</record>
<!-- Sequences -->
@ -344,6 +320,7 @@
<menuitem id="next_id_6" name="Actions" parent="base.next_id_4" sequence="1"/>
<menuitem action="ir_sequence_actions" id="menu_ir_sequence_actions" parent="next_id_6"/>
<menuitem action="act_values_form_action" id="menu_values_form_action" parent="next_id_6"/>
<menuitem action="act_values_form_defaults" id="menu_values_form_defaults" parent="next_id_6"/>
<!--Filters form view-->

View File

@ -236,15 +236,18 @@ class ir_mail_server(osv.osv):
return connection
def build_email(self, email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False,
attachments=None, message_id=None, references=None, object_id=False, subtype='plain', headers=None):
attachments=None, message_id=None, references=None, object_id=False, subtype='plain', headers=None,
body_alternative=None, subtype_alternative='plain'):
"""Constructs an RFC2822 email.message.Message object based on the keyword arguments passed, and returns it.
:param string email_from: sender email address
:param list email_to: list of recipient addresses (to be joined with commas)
:param string subject: email subject (no pre-encoding/quoting necessary)
:param string body: email body, according to the ``subtype`` (by default, plaintext).
:param string body: email body, of the type ``subtype`` (by default, plaintext).
If html subtype is used, the message will be automatically converted
to plaintext and wrapped in multipart/alternative.
to plaintext and wrapped in multipart/alternative, unless an explicit
``body_alternative`` version is passed.
:param string body_alternative: optional alternative body, of the type specified in ``subtype_alternative``
:param string reply_to: optional value of Reply-To header
:param string object_id: optional tracking identifier, to be included in the message-id for
recognizing replies. Suggested format for object-id is "res_id-model",
@ -252,6 +255,8 @@ class ir_mail_server(osv.osv):
:param string subtype: optional mime subtype for the text body (usually 'plain' or 'html'),
must match the format of the ``body`` parameter. Default is 'plain',
making the content part of the mail "text/plain".
:param string subtype_alternative: optional mime subtype of ``body_alternative`` (usually 'plain'
or 'html'). Default is 'plain'.
:param list attachments: list of (filename, filecontents) pairs, where filecontents is a string
containing the bytes of the attachment
:param list email_cc: optional list of string values for CC header (to be joined with commas)
@ -276,7 +281,7 @@ class ir_mail_server(osv.osv):
if not body: body = u''
email_body_utf8 = ustr(body).encode('utf-8')
email_text_part = MIMEText(email_body_utf8 or '', _subtype=subtype, _charset='utf-8')
email_text_part = MIMEText(email_body_utf8, _subtype=subtype, _charset='utf-8')
msg = MIMEMultipart()
if not message_id:
@ -304,13 +309,21 @@ class ir_mail_server(osv.osv):
for key, value in headers.iteritems():
msg[ustr(key).encode('utf-8')] = encode_header(value)
if html2text and subtype == 'html':
# Always provide alternative text body if possible.
if subtype == 'html' and not body_alternative and html2text:
# Always provide alternative text body ourselves if possible.
text_utf8 = tools.html2text(email_body_utf8.decode('utf-8')).encode('utf-8')
alternative_part = MIMEMultipart(_subtype="alternative")
alternative_part.attach(MIMEText(text_utf8, _charset='utf-8', _subtype='plain'))
alternative_part.attach(email_text_part)
msg.attach(alternative_part)
elif body_alternative:
# Include both alternatives, as specified, within a multipart/alternative part
alternative_part = MIMEMultipart(_subtype="alternative")
body_alternative_utf8 = ustr(body_alternative).encode('utf-8')
alternative_body_part = MIMEText(body_alternative_utf8, _subtype=subtype_alternative, _charset='utf-8')
alternative_part.attach(alternative_body_part)
alternative_part.attach(email_text_part)
msg.attach(alternative_part)
else:
msg.attach(email_text_part)

View File

@ -28,19 +28,89 @@ EXCLUDED_FIELDS = set((
'report_sxw_content', 'report_rml_content', 'report_sxw', 'report_rml',
'report_sxw_content_data', 'report_rml_content_data', 'search_view', ))
#: Possible slots to bind an action to with :meth:`~.set_action`
ACTION_SLOTS = [
"client_action_multi", # sidebar wizard action
"client_print_multi", # sidebar report printing button
"client_action_relate", # sidebar related link
"tree_but_open", # double-click on item in tree view
"tree_but_action", # deprecated: same as tree_but_open
]
class ir_values(osv.osv):
"""Holds internal model-specific action bindings and user-defined default
field values. definitions. This is a legacy internal model, mixing
two different concepts, and will likely be updated or replaced in a
future version by cleaner, separate models. You should not depend
explicitly on it.
The purpose of each ``ir.values`` entry depends on its type, defined
by the ``key`` column:
* 'default': user-defined default values, used when creating new
records of this model:
* 'action': binding of an action to a particular *action slot* of
this model, making the action easily available in the user
interface for this model.
The ``key2`` column acts as a qualifier, further refining the type
of the entry. The possible values are:
* for 'default' entries: an optional condition restricting the
cases where this particular default value will be applicable,
or ``False`` for no condition
* for 'action' entries: the ``key2`` qualifier is one of the available
action slots, defining how this action can be invoked:
* ``'client_print_multi'`` for report printing actions that will
be available on views displaying items from this model
* ``'client_action_multi'`` for assistants (wizards) actions
that will be available in views displaying objects of this model
* ``'client_action_relate'`` for links towards related documents
that should be available in views displaying objects of this model
* ``'tree_but_open'`` for actions that will be triggered when
double-clicking an item from this model in a hierarchical tree view
Each entry is specific to a model (``model`` column), and for ``'actions'``
type, may even be made specific to a given record of that model when the
``res_id`` column contains a record ID (``False`` means it's global for
all records).
The content of the entry is defined by the ``value`` column, which may either
contain an arbitrary value, or a reference string defining the action that
should be executed.
.. rubric:: Usage: default values
The ``'default'`` entries are usually defined manually by the
users, and set by their UI clients calling :meth:`~.set_default`.
These default values are then automatically used by the
ORM every time a new record is about to be created, i.e. when
:meth:`~openerp.osv.osv.osv.default_get`
or :meth:`~openerp.osv.osv.osv.create` are called.
.. rubric:: Usage: action bindings
Business applications will usually bind their actions during
installation, and OpenERP UI clients will apply them as defined,
based on the list of actions included in the result of
:meth:`~openerp.osv.osv.osv.fields_view_get`,
or directly returned by explicit calls to :meth:`~.get_actions`.
"""
_name = 'ir.values'
def _value_unpickle(self, cursor, user, ids, name, arg, context=None):
res = {}
for report in self.browse(cursor, user, ids, context=context):
value = report[name[:-9]]
if not report.object and value:
for record in self.browse(cursor, user, ids, context=context):
value = record[name[:-9]]
if record.key == 'default' and value:
# default values are pickled on the fly
try:
value = str(pickle.loads(value))
except:
except Exception:
pass
res[report.id] = value
res[record.id] = value
return res
def _value_pickle(self, cursor, user, id, name, value, arg, context=None):
@ -49,18 +119,20 @@ class ir_values(osv.osv):
ctx = context.copy()
if self.CONCURRENCY_CHECK_FIELD in ctx:
del ctx[self.CONCURRENCY_CHECK_FIELD]
if not self.browse(cursor, user, id, context=context).object:
record = self.browse(cursor, user, id, context=context)
if record.key == 'default':
# default values are pickled on the fly
value = pickle.dumps(value)
self.write(cursor, user, id, {name[:-9]: value}, context=ctx)
def onchange_object_id(self, cr, uid, ids, object_id, context={}):
def onchange_object_id(self, cr, uid, ids, object_id, context=None):
if not object_id: return {}
act = self.pool.get('ir.model').browse(cr, uid, object_id, context=context)
return {
'value': {'model': act.model}
}
def onchange_action_id(self, cr, uid, ids, action_id, context={}):
def onchange_action_id(self, cr, uid, ids, action_id, context=None):
if not action_id: return {}
act = self.pool.get('ir.actions.actions').browse(cr, uid, action_id, context=context)
return {
@ -68,32 +140,47 @@ class ir_values(osv.osv):
}
_columns = {
'name': fields.char('Name', size=128),
'model_id': fields.many2one('ir.model', 'Object', size=128,
help="This field is not used, it only helps you to select a good model."),
'model': fields.char('Object Name', size=128, select=True),
'action_id': fields.many2one('ir.actions.actions', 'Action',
help="This field is not used, it only helps you to select the right action."),
'value': fields.text('Value'),
'name': fields.char('Name', size=128, required=True),
'model': fields.char('Model Name', size=128, select=True, required=True,
help="Model to which this entry applies"),
# TODO: model_id and action_id should be read-write function fields
'model_id': fields.many2one('ir.model', 'Model (change only)', size=128,
help="Model to which this entry applies - "
"helper field for setting a model, will "
"automatically set the correct model name"),
'action_id': fields.many2one('ir.actions.actions', 'Action (change only)',
help="Action bound to this entry - "
"helper field for binding an action, will "
"automatically set the correct reference"),
'value': fields.text('Value', help="Default value (pickled) or reference to an action"),
'value_unpickle': fields.function(_value_unpickle, fnct_inv=_value_pickle,
method=True, type='text', string='Value'),
'object': fields.boolean('Is Object'),
'key': fields.selection([('action','Action'),('default','Default')], 'Type', size=128, select=True),
'key2' : fields.char('Event Type', size=128, select=True, help="The kind of action or button on the client side "
"that will trigger the action. One of: "
"client_action_multi, client_action_relate, tree_but_open, "
"client_print_multi"),
'meta': fields.text('Meta Datas'),
'meta_unpickle': fields.function(_value_unpickle, fnct_inv=_value_pickle,
method=True, type='text', string='Metadata'),
'res_id': fields.integer('Object ID', help="Keep 0 if the action must appear on all resources.", select=True),
'user_id': fields.many2one('res.users', 'User', ondelete='cascade', select=True),
'company_id': fields.many2one('res.company', 'Company', select=True)
type='text',
string='Default value or action reference'),
'key': fields.selection([('action','Action'),('default','Default')],
'Type', size=128, select=True, required=True,
help="- Action: an action attached to one slot of the given model\n"
"- Default: a default value for a model field"),
'key2' : fields.char('Qualifier', size=128, select=True,
help="For actions, one of the possible action slots: \n"
" - client_action_multi\n"
" - client_print_multi\n"
" - client_action_relate\n"
" - tree_but_open\n"
"For defaults, an optional condition"
,),
'res_id': fields.integer('Record ID', select=True,
help="Database identifier of the record to which this applies. "
"0 = for all records"),
'user_id': fields.many2one('res.users', 'User', ondelete='cascade', select=True,
help="If set, action binding only applies for this user."),
'company_id': fields.many2one('res.company', 'Company', ondelete='cascade', select=True,
help="If set, action binding only applies for this company")
}
_defaults = {
'key': lambda *a: 'action',
'key2': lambda *a: 'tree_but_open',
'company_id': lambda *a: False
'key': 'action',
'key2': 'tree_but_open',
}
def _auto_init(self, cr, context=None):
@ -102,140 +189,271 @@ class ir_values(osv.osv):
if not cr.fetchone():
cr.execute('CREATE INDEX ir_values_key_model_key2_res_id_user_id_idx ON ir_values (key, model, key2, res_id, user_id)')
def set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=False, preserve_user=False, company=False):
def set_default(self, cr, uid, model, field_name, value, for_all_users=True, company_id=False, condition=False):
"""Defines a default value for the given model and field_name. Any previous
default for the same scope (model, field_name, value, for_all_users, company_id, condition)
will be replaced and lost in the process.
Defaults can be later retrieved via :meth:`~.get_defaults`, which will return
the highest priority default for any given field. Defaults that are more specific
have a higher priority, in the following order (highest to lowest):
* specific to user and company
* specific to user only
* specific to company only
* global to everyone
:param string model: model name
:param string field_name: field name to which the default applies
:param value: the default field value to set
:type value: any serializable Python value
:param bool for_all_users: whether the default should apply to everybody or only
the user calling the method
:param int company_id: optional ID of the company to which the default should
apply. If omitted, the default will be global. If True
is passed, the current user's company will be used.
:param string condition: optional condition specification that can be used to
restrict the applicability of the default values
(e.g. based on another field's value). This is an
opaque string as far as the API is concerned, but client
stacks typically use single-field conditions in the
form ``'key=stringified_value'``.
(Currently, the condition is trimmed to 200 characters,
so values that share the same first 200 characters always
match)
:return: id of the newly created ir.values entry
"""
if isinstance(value, unicode):
value = value.encode('utf8')
if not isobject:
value = pickle.dumps(value)
if meta:
meta = pickle.dumps(meta)
assert isinstance(models, (list, tuple)), models
ids_res = []
for model in models:
if isinstance(model, (list, tuple)):
model,res_id = model
else:
res_id = False
if replace:
search_criteria = [
('key', '=', key),
('key2', '=', key2),
('model', '=', model),
('res_id', '=', res_id),
('user_id', '=', preserve_user and uid)
]
if key in ('meta', 'default'):
search_criteria.append(('name', '=', name))
else:
search_criteria.append(('value', '=', value))
if company_id is True:
# should be company-specific, need to get company id
user = self.pool.get('res.users').browse(cr, uid, uid)
company_id = user.company_id.id
self.unlink(cr, uid, self.search(cr, uid, search_criteria))
vals = {
'name': name,
'value': value,
'model': model,
'object': isobject,
'key': key,
'key2': key2 and key2[:200],
'meta': meta,
'user_id': preserve_user and uid,
}
if company:
cid = self.pool.get('res.users').browse(cr, uid, uid, context={}).company_id.id
vals['company_id']=cid
if res_id:
vals['res_id']= res_id
ids_res.append(self.create(cr, uid, vals))
return ids_res
# remove existing defaults for the same scope
search_criteria = [
('key', '=', 'default'),
('key2', '=', condition and condition[:200]),
('model', '=', model),
('name', '=', field_name),
('user_id', '=', False if for_all_users else uid),
('company_id','=', company_id)
]
self.unlink(cr, uid, self.search(cr, uid, search_criteria))
return self.create(cr, uid, {
'name': field_name,
'value': pickle.dumps(value),
'model': model,
'key': 'default',
'key2': condition and condition[:200],
'user_id': False if for_all_users else uid,
'company_id': company_id,
})
def get_defaults(self, cr, uid, model, condition=False):
"""Returns any default values that are defined for the current model and user,
(and match ``condition``, if specified), previously registered via
:meth:`~.set_default`.
Defaults are global to a model, not field-specific, but an optional
``condition`` can be provided to restrict matching default values
to those that were defined for the same condition (usually based
on another field's value).
Default values also have priorities depending on whom they apply
to: only the highest priority value will be returned for any
field. See :meth:`~.set_default` for more details.
:param string model: model name
:param string condition: optional condition specification that can be used to
restrict the applicability of the default values
(e.g. based on another field's value). This is an
opaque string as far as the API is concerned, but client
stacks typically use single-field conditions in the
form ``'key=stringified_value'``.
(Currently, the condition is trimmed to 200 characters,
so values that share the same first 200 characters always
match)
:return: list of default values tuples of the form ``(id, field_name, value)``
(``id`` is the ID of the default entry, usually irrelevant)
"""
# use a direct SQL query for performance reasons,
# this is called very often
query = """SELECT v.id, v.name, v.value FROM ir_values v
LEFT JOIN res_users u ON (v.user_id = u.id)
WHERE v.key = %%s AND v.model = %%s
AND (v.user_id = %%s OR v.user_id IS NULL)
AND (v.company_id IS NULL OR
v.company_id =
(SELECT company_id from res_users where id = %%s)
)
%s
ORDER BY v.user_id, u.company_id"""
query = query % ('AND v.key2 = %s' if condition else '')
params = ('default', model, uid, uid)
if condition:
params += (condition[:200],)
cr.execute(query, params)
# keep only the highest priority default for each field
defaults = {}
for row in cr.dictfetchall():
defaults.setdefault(row['name'],
(row['id'], row['name'], pickle.loads(row['value'].encode('utf-8'))))
return defaults.values()
def set_action(self, cr, uid, name, action_slot, model, action, res_id=False):
"""Binds an the given action to the given model's action slot - for later
retrieval via :meth:`~.get_actions`. Any existing binding of the same action
to the same slot is first removed, allowing an update of the action's name.
See the class description for more details about the various action
slots: :class:`~ir_values`.
:param string name: action label, usually displayed by UI client
:param string action_slot: the action slot to which the action should be
bound to - one of ``client_action_multi``,
``client_print_multi``, ``client_action_relate``,
``tree_but_open``.
:param string model: model name
:param string action: action reference, in the form ``'model,id'``
:param int res_id: optional record id - will bind the action only to a
specific record of the model, not all records.
:return: id of the newly created ir.values entry
"""
assert isinstance(action, basestring) and ',' in action, \
'Action definition must be an action reference, e.g. "ir.actions.act_window,42"'
assert action_slot in ACTION_SLOTS, \
'Action slot (%s) must be one of: %r' % (action_slot, ACTION_SLOTS)
# remove existing action definition of same slot and value
search_criteria = [
('key', '=', 'action'),
('key2', '=', action_slot),
('model', '=', model),
('res_id', '=', res_id or 0), # int field -> NULL == 0
('value', '=', action),
]
self.unlink(cr, uid, self.search(cr, uid, search_criteria))
return self.create(cr, uid, {
'key': 'action',
'key2': action_slot,
'model': model,
'res_id': res_id,
'name': name,
'value': action,
})
def get_actions(self, cr, uid, action_slot, model, res_id=False, context=None):
"""Retrieves the list of actions bound to the given model's action slot.
See the class description for more details about the various action
slots: :class:`~.ir_values`.
:param string action_slot: the action slot to which the actions should be
bound to - one of ``client_action_multi``,
``client_print_multi``, ``client_action_relate``,
``tree_but_open``.
:param string model: model name
:param int res_id: optional record id - will bind the action only to a
specific record of the model, not all records.
:return: list of action tuples of the form ``(id, name, action_def)``,
where ``id`` is the ID of the default entry, ``name`` is the
action label, and ``action_def`` is a dict containing the
action definition as obtained by calling
:meth:`~openerp.osv.osv.osv.read` on the action record.
"""
assert action_slot in ACTION_SLOTS, 'Illegal action slot value: %s' % action_slot
# use a direct SQL query for performance reasons,
# this is called very often
query = """SELECT v.id, v.name, v.value FROM ir_values v
WHERE v.key = %s AND v.key2 = %s
AND v.model = %s
AND (v.res_id = %s
OR v.res_id IS NULL
OR v.res_id = 0)
ORDER BY v.id"""
cr.execute(query, ('action', action_slot, model, res_id or None))
results = {}
for action in cr.dictfetchall():
action_model,id = action['value'].split(',')
fields = [
field
for field in self.pool.get(action_model)._all_columns
if field not in EXCLUDED_FIELDS]
# FIXME: needs cleanup
try:
action_def = self.pool.get(action_model).read(cr, uid, int(id), fields, context)
if action_def:
if action_model in ('ir.actions.report.xml','ir.actions.act_window',
'ir.actions.wizard'):
groups = action_def.get('groups_id')
if groups:
cr.execute('SELECT 1 FROM res_groups_users_rel WHERE gid IN %s AND uid=%s',
(tuple(groups), uid))
if not cr.fetchone():
if action['name'] == 'Menuitem':
raise osv.except_osv('Error !',
'You do not have the permission to perform this operation !!!')
continue
# keep only the first action registered for each action name
results[action['name']] = (action['id'], action['name'], action_def)
except except_orm, e:
continue
return results.values()
def _map_legacy_model_list(self, model_list, map_fn, merge_results=False):
"""Apply map_fn to the various models passed, according to
legacy way to specify models/records.
"""
assert isinstance(model_list, (list, tuple)), \
"model_list should be in the form [model,..] or [(model,res_id), ..]"
results = []
for model in model_list:
res_id = False
if isinstance(model, (list, tuple)):
model, res_id = model
result = map_fn(model, res_id)
# some of the functions return one result at a time (tuple or id)
# and some return a list of many of them - care for both
if merge_results:
results.extend(result)
else:
results.append(result)
return results
# Backards-compatibility adapter layer to retrofit into split API
def set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=False, preserve_user=False, company=False):
"""Deprecated legacy method to set default values and bind actions to models' action slots.
Now dispatches to the newer API methods according to the value of ``key``: :meth:`~.set_default`
(``key=='default'``) or :meth:`~.set_action` (``key == 'action'``).
:deprecated: As of v6.1, ``set_default()`` or ``set_action()`` should be used directly.
"""
assert key in ['default', 'action'], "ir.values entry keys must be in ['default','action']"
if key == 'default':
def do_set(model,res_id):
return self.set_default(cr, uid, model, field_name=name, value=value,
for_all_users=(not preserve_user), company_id=company,
condition=key2)
elif key == 'action':
def do_set(model,res_id):
return self.set_action(cr, uid, name, action_slot=key2, model=model, action=value, res_id=res_id)
return self._map_legacy_model_list(models, do_set)
def get(self, cr, uid, key, key2, models, meta=False, context=None, res_id_req=False, without_user=True, key2_req=True):
if context is None:
context = {}
result = []
assert isinstance(models, (list, tuple)), models
"""Deprecated legacy method to get the list of default values or actions bound to models' action slots.
Now dispatches to the newer API methods according to the value of ``key``: :meth:`~.get_defaults`
(``key=='default'``) or :meth:`~.get_actions` (``key == 'action'``)
for m in models:
if isinstance(m, (list, tuple)):
m, res_id = m
else:
res_id = False
:deprecated: As of v6.1, ``get_defaults()`` or ``get_actions()`` should be used directly.
where = ['key=%s','model=%s']
params = [key, str(m)]
if key2:
where.append('key2=%s')
params.append(key2[:200])
elif key2_req and not meta:
where.append('key2 is null')
if res_id_req and (models[-1][0] == m):
if res_id:
where.append('res_id=%s')
params.append(res_id)
else:
where.append('(res_id is NULL)')
elif res_id:
if (models[-1][0]==m):
where.append('(res_id=%s or (res_id is null))')
params.append(res_id)
else:
where.append('res_id=%s')
params.append(res_id)
order = 'id'
if key == 'default':
# Make sure we get first the values for specific users, then
# the global values. The map/filter below will retain the first
# value for any given name. The 'order by' will put the null
# values last; this may be postgres specific (it is the
# behavior in postgres at least since 8.2).
order = 'user_id'
where.append('(user_id=%s or (user_id IS NULL)) order by '+ order)
params.append(uid)
clause = ' and '.join(where)
cr.execute('select id,name,value,object,meta, key from ir_values where ' + clause, params)
result = cr.fetchall()
if result:
break
if not result:
return []
def _result_get(x, keys):
if x[1] in keys:
return False
keys.append(x[1])
if x[3]:
model,id = x[2].split(',')
# FIXME: It might be a good idea to opt-in that kind of stuff
# FIXME: instead of arbitrarily removing random fields
fields = [
field
for field in self.pool.get(model).fields_get_keys(cr, uid)
if field not in EXCLUDED_FIELDS]
try:
datas = self.pool.get(model).read(cr, uid, [int(id)], fields, context)
except except_orm, e:
return False
datas = datas and datas[0]
if not datas:
return False
else:
datas = pickle.loads(x[2].encode('utf-8'))
if meta:
return (x[0], x[1], datas, pickle.loads(x[4]))
return (x[0], x[1], datas)
keys = []
res = filter(None, map(lambda x: _result_get(x, keys), result))
res2 = res[:]
for r in res:
if isinstance(r[2], dict) and r[2].get('type') in ('ir.actions.report.xml','ir.actions.act_window','ir.actions.wizard'):
groups = r[2].get('groups_id')
if groups:
cr.execute('SELECT COUNT(1) FROM res_groups_users_rel WHERE gid IN %s AND uid=%s',(tuple(groups), uid))
cnt = cr.fetchone()[0]
if not cnt:
res2.remove(r)
if r[1] == 'Menuitem' and not res2:
raise osv.except_osv('Error !','You do not have the permission to perform this operation !!!')
return res2
ir_values()
"""
assert key in ['default', 'action'], "ir.values entry keys must be in ['default','action']"
if key == 'default':
def do_get(model,res_id):
return self.get_defaults(cr, uid, model, condition=key2)
elif key == 'action':
def do_get(model,res_id):
return self.get_actions(cr, uid, action_slot=key2, model=model, res_id=res_id, context=context)
return self._map_legacy_model_list(models, do_get, merge_results=True)

View File

@ -52,7 +52,7 @@ ACTION_DICT = {
class module_category(osv.osv):
_name = "ir.module.category"
_description = "Module Category"
_description = "Application"
def _module_nbr(self,cr,uid, ids, prop, unknow_none, context):
cr.execute('SELECT category_id, COUNT(*) \
@ -70,29 +70,16 @@ class module_category(osv.osv):
result.get(id, 0))
return result
def name_get(self, cr, uid, ids, context=None):
result = []
reads = self.read(cr, uid, ids, ['name', 'parent_id'], context=context)
for record in reads:
name = record['name']
if record['parent_id']:
name = record['parent_id'][1] + ' / ' + name
result.append((record['id'], name,))
return result
_columns = {
'name': fields.char("Name", size=128, required=True, select=True),
'parent_id': fields.many2one('ir.module.category', 'Parent Category', select=True),
'child_ids': fields.one2many('ir.module.category', 'parent_id', 'Child Categories'),
'parent_id': fields.many2one('ir.module.category', 'Parent Application', select=True),
'child_ids': fields.one2many('ir.module.category', 'parent_id', 'Child Applications'),
'module_nr': fields.function(_module_nbr, method=True, string='Number of Modules', type='integer'),
'module_ids' : fields.one2many('ir.module.module', 'category_id', 'Modules'),
'description' : fields.text("Description"),
'sequence' : fields.integer('Sequence'),
}
_order = 'name'
module_category()
class module(osv.osv):
_name = "ir.module.module"
@ -233,6 +220,7 @@ class module(osv.osv):
'demo': False,
'license': 'AGPL-3',
'web': False,
'complexity': 'normal',
}
_order = 'name'

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import re
import time
import netsvc
from osv import fields, osv
@ -26,6 +27,8 @@ import tools
from tools.misc import currency
from tools.translate import _
CURRENCY_DISPLAY_PATTERN = re.compile(r'(\w+)\s*(?:\((.*)\))?')
class res_currency(osv.osv):
def _current_rate(self, cr, uid, ids, name, arg, context=None):
if context is None:
@ -68,6 +71,8 @@ class res_currency(osv.osv):
_defaults = {
'active': lambda *a: 1,
'position' : 'after',
'rounding': 0.01,
'accuracy': 4,
}
_sql_constraints = [
# this constraint does not cover all cases due to SQL NULL handling for company_id,
@ -101,6 +106,18 @@ class res_currency(osv.osv):
r['date'] = currency_date
return res
def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
if not args:
args = []
if name:
ids = self.search(cr, user, ([('name','=',name)] + args), limit=limit, context=context)
name_match = CURRENCY_DISPLAY_PATTERN.match(name)
if not ids and name_match:
ids = self.search(cr, user, [('name','=', name_match.group(1))] + args, limit=limit, context=context)
else:
ids = self.search(cr, user, args, limit=limit, context=context)
return self.name_get(cr, user, ids, context=context)
def name_get(self, cr, uid, ids, context=None):
if not ids:
return []

View File

@ -185,7 +185,7 @@ class res_partner(osv.osv):
def name_get(self, cr, uid, ids, context={}):
if not len(ids):
return []
if context.get('show_ref', False):
if context and context.get('show_ref'):
rec_name = 'ref'
else:
rec_name = 'name'
@ -196,8 +196,6 @@ class res_partner(osv.osv):
def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):
if not args:
args=[]
if not context:
context={}
if name:
ids = self.search(cr, uid, [('ref', '=', name)] + args, limit=limit, context=context)
if not ids:
@ -312,6 +310,8 @@ class res_partner_address(osv.osv):
}
def name_get(self, cr, user, ids, context={}):
if context is None:
context = {}
if not len(ids):
return []
res = []

View File

@ -69,6 +69,13 @@
<field name="domain_force">[('company_id','child_of',[user.company_id.id])]</field>
</record>
<record model="ir.rule" id="ir_values_default_rule">
<field name="name">Defaults: alter personal values only</field>
<field name="model_id" ref="model_ir_values"/>
<field name="domain_force">[('key','=','default'),('user_id','=',user.id)]</field>
<field name="perm_read" eval="False"/>
</record>
</data>
</openerp>

View File

@ -38,8 +38,7 @@
"access_ir_ui_view_custom_group_user","ir_ui_view_custom_group_user","model_ir_ui_view_custom",,1,0,0,0
"access_ir_ui_view_custom_group_system","ir_ui_view_custom_group_system","model_ir_ui_view_custom","group_system",1,1,1,1
"access_ir_ui_view_sc_group_user","ir_ui_view_sc group_user","model_ir_ui_view_sc",,1,1,1,1
"access_ir_values_group_erp_manager","ir_values group_erp_manager","model_ir_values","group_erp_manager",1,1,1,1
"access_ir_values_group_all","ir_values group_all","model_ir_values",,1,0,1,0
"access_ir_values_group_all","ir_values group_all","model_ir_values",,1,1,1,1
"access_res_company_group_erp_manager","res_company group_erp_manager","model_res_company","group_erp_manager",1,1,1,1
"access_res_company_group_user","res_company group_user","model_res_company",,1,0,0,0
"access_res_country_group_all","res_country group_user_all","model_res_country",,1,0,0,0

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
38 access_ir_ui_view_custom_group_user ir_ui_view_custom_group_user model_ir_ui_view_custom 1 0 0 0
39 access_ir_ui_view_custom_group_system ir_ui_view_custom_group_system model_ir_ui_view_custom group_system 1 1 1 1
40 access_ir_ui_view_sc_group_user ir_ui_view_sc group_user model_ir_ui_view_sc 1 1 1 1
41 access_ir_values_group_erp_manager access_ir_values_group_all ir_values group_erp_manager ir_values group_all model_ir_values group_erp_manager 1 1 1 1
access_ir_values_group_all ir_values group_all model_ir_values 1 0 1 0
42 access_res_company_group_erp_manager res_company group_erp_manager model_res_company group_erp_manager 1 1 1 1
43 access_res_company_group_user res_company group_user model_res_company 1 0 0 0
44 access_res_country_group_all res_country group_user_all model_res_country 1 0 0 0

View File

@ -2,24 +2,71 @@
Create some default value for some (non-existing) model, for all users.
-
!python {model: ir.values }: |
self.set(cr, uid, 'default', False, 'my_test_ir_value',['unexisting_model'], 'global value')
self.set(cr, uid, 'default', False, 'my_test_field',['unexisting_model'], 'global value')
-
Retrieve it.
-
!python {model: ir.values }: |
# d is a list of triple (id, name, value)
d = self.get(cr, uid, 'default', False, ['unexisting_model'])
assert d[0][1] == u'my_test_ir_value', "Can't retrieve the created default value."
assert d[0][1] == 'my_test_field', "Can't retrieve the created default value."
assert d[0][2] == 'global value', "Can't retrieve the created default value."
-
Do it again but for a specific user.
-
!python {model: ir.values }: |
self.set(cr, uid, 'default', False, 'my_test_ir_value',['unexisting_model'], 'specific value', preserve_user=True)
self.set(cr, uid, 'default', False, 'my_test_field',['unexisting_model'], 'specific value', preserve_user=True)
-
Retrieve it and check it is the one for the current user.
-
!python {model: ir.values }: |
d = self.get(cr, uid, 'default', False, ['unexisting_model'])
assert d[0][1] == u'my_test_ir_value', "Can't retrieve the created default value."
assert len(d) == 1, "Only one default must be returned per field"
assert d[0][1] == 'my_test_field', "Can't retrieve the created default value."
assert d[0][2] == 'specific value', "Can't retrieve the created default value."
-
Create some action bindings for a non-existing model
-
!python {model: ir.values }: |
self.set(cr, uid, 'action', 'tree_but_open', 'OnDblClick Action', ['unexisting_model'], 'ir.actions.act_window,10', isobject=True)
self.set(cr, uid, 'action', 'tree_but_open', 'OnDblClick Action 2', ['unexisting_model'], 'ir.actions.act_window,11', isobject=True)
self.set(cr, uid, 'action', 'client_action_multi', 'Side Wizard', ['unexisting_model'], 'ir.actions.act_window,12', isobject=True)
self.set(cr, uid, 'action', 'client_print_multi', 'Nice Report', ['unexisting_model'], 'ir.actions.report.xml,2', isobject=True)
self.set(cr, uid, 'action', 'client_action_relate', 'Related Stuff', ['unexisting_model'], 'ir.actions.act_window,14', isobject=True)
-
Replace one action binding to set a new name
-
!python {model: ir.values }: |
self.set(cr, uid, 'action', 'tree_but_open', 'OnDblClick Action New', ['unexisting_model'], 'ir.actions.act_window,10', isobject=True)
-
Retrieve the action bindings and check they're correct
-
!python {model: ir.values }: |
actions = self.get(cr, uid, 'action', 'tree_but_open', ['unexisting_model'])
assert len(actions) == 2, "Mismatching number of bound actions"
#first action
assert len(actions[0]) == 3, "Malformed action definition"
assert actions[0][1] == 'OnDblClick Action 2', 'Bound action does not match definition'
assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == 11, 'Bound action does not match definition'
#second action - this ones comes last because it was re-created with a different name
assert len(actions[1]) == 3, "Malformed action definition"
assert actions[1][1] == 'OnDblClick Action New', 'Re-Registering an action should replace it'
assert isinstance(actions[1][2], dict) and actions[1][2]['id'] == 10, 'Bound action does not match definition'
actions = self.get(cr, uid, 'action', 'client_action_multi', ['unexisting_model'])
assert len(actions) == 1, "Mismatching number of bound actions"
assert len(actions[0]) == 3, "Malformed action definition"
assert actions[0][1] == 'Side Wizard', 'Bound action does not match definition'
assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == 12, 'Bound action does not match definition'
actions = self.get(cr, uid, 'action', 'client_print_multi', ['unexisting_model'])
assert len(actions) == 1, "Mismatching number of bound actions"
assert len(actions[0]) == 3, "Malformed action definition"
assert actions[0][1] == 'Nice Report', 'Bound action does not match definition'
assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == 2, 'Bound action does not match definition'
actions = self.get(cr, uid, 'action', 'client_action_relate', ['unexisting_model'])
assert len(actions) == 1, "Mismatching number of bound actions"
assert len(actions[0]) == 3, "Malformed action definition"
assert actions[0][1] == 'Related Stuff', 'Bound action does not match definition'
assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == 14, 'Bound action does not match definition'

View File

@ -249,7 +249,7 @@ def load_information_from_description_file(module):
info.setdefault('website', '')
info.setdefault('name', False)
info.setdefault('description', '')
info.setdefault('complexity', False)
info.setdefault('complexity', 'normal')
info['certificate'] = info.get('certificate') or None
info['web'] = info.get('web') or False
info['license'] = info.get('license') or 'AGPL-3'

View File

@ -156,9 +156,21 @@ class integer_big(_column):
class reference(_column):
_type = 'reference'
_classic_read = False # post-process to handle missing target
def __init__(self, string, selection, size, **args):
_column.__init__(self, string=string, size=size, selection=selection, **args)
def get(self, cr, obj, ids, name, uid=None, context=None, values=None):
result = {}
# copy initial values fetched previously.
for value in values:
result[value['id']] = value[name]
if value[name]:
model, res_id = value[name].split(',')
if not obj.pool.get(model).exists(cr, uid, [int(res_id)], context=context):
result[value['id']] = False
return result
class char(_column):
_type = 'char'
@ -495,6 +507,15 @@ class one2many(_column):
class many2many(_column):
"""Encapsulates the logic of a many-to-many bidirectional relationship, handling the
low-level details of the intermediary relationship table transparently.
A many-to-many relationship is always symmetrical, and can be declared and accessed
from either endpoint model.
If ``rel`` (relationship table name), ``id1`` (source foreign key column name)
or id2 (destination foreign key column name) are not specified, the system will
provide default values. This will by default only allow one single symmetrical
many-to-many relationship between the source and destination model.
For multiple many-to-many relationship between the same models and for
relationships where source and destination models are the same, ``rel``, ``id1``
and ``id2`` should be specified explicitly.
:param str obj: destination model
:param str rel: optional name of the intermediary relationship table. If not specified,

View File

@ -44,6 +44,7 @@
import calendar
import copy
import datetime
import itertools
import logging
import operator
import pickle
@ -303,7 +304,8 @@ class browse_record(object):
self._cr = cr
self._uid = uid
self._id = id
self._table = table
self._table = table # deprecated, use _model!
self._model = table
self._table_name = self._table._name
self.__logger = logging.getLogger(
'osv.browse_record.' + self._table_name)
@ -460,6 +462,9 @@ class browse_record(object):
def __contains__(self, name):
return (name in self._table._columns) or (name in self._table._inherit_fields) or hasattr(self._table, name)
def __iter__(self):
raise NotImplementedError("Iteration is not allowed on %s" % self)
def __hasattr__(self, name):
return name in self
@ -815,13 +820,16 @@ class BaseModel(object):
raise TypeError('_name is mandatory in case of multiple inheritance')
for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]):
parent_class = pool.get(parent_name).__class__
if not pool.get(parent_name):
parent_model = pool.get(parent_name)
if not getattr(cls, '_original_module', None) and name == parent_model._name:
cls._original_module = parent_model._original_module
if not parent_model:
raise TypeError('The model "%s" specifies an unexisting parent class "%s"\n'
'You may need to add a dependency on the parent class\' module.' % (name, parent_name))
parent_class = parent_model.__class__
nattr = {}
for s in attributes:
new = copy.copy(getattr(pool.get(parent_name), s, {}))
new = copy.copy(getattr(parent_model, s, {}))
if s == '_columns':
# Don't _inherit custom fields.
for c in new.keys():
@ -849,6 +857,8 @@ class BaseModel(object):
new.extend(cls.__dict__.get(s, []))
nattr[s] = new
cls = type(name, (cls, parent_class), dict(nattr, _register=False))
if not getattr(cls, '_original_module', None):
cls._original_module = cls._module
obj = object.__new__(cls)
obj.__init__(pool, cr)
return obj
@ -912,14 +922,17 @@ class BaseModel(object):
f = self._columns[store_field]
if hasattr(f, 'digits_change'):
f.digits_change(cr)
def not_this_field(stored_func):
x, y, z, e, f, l = stored_func
return x != self._name or y != store_field
self.pool._store_function[self._name] = filter(not_this_field, self.pool._store_function.get(self._name, []))
if not isinstance(f, fields.function):
continue
if not f.store:
continue
if self._columns[store_field].store is True:
sm = f.store
if sm is True:
sm = {self._name: (lambda self, cr, uid, ids, c={}: ids, None, 10, None)}
else:
sm = self._columns[store_field].store
for object, aa in sm.items():
if len(aa) == 4:
(fnct, fields2, order, length) = aa
@ -930,14 +943,8 @@ class BaseModel(object):
raise except_orm('Error',
('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority, time length)}.' % (store_field, self._name)))
self.pool._store_function.setdefault(object, [])
ok = True
for x, y, z, e, f, l in self.pool._store_function[object]:
if (x==self._name) and (y==store_field) and (e==fields2):
if f == order:
ok = False
if ok:
self.pool._store_function[object].append( (self._name, store_field, fnct, tuple(fields2) if fields2 else None, order, length))
self.pool._store_function[object].sort(lambda x, y: cmp(x[4], y[4]))
self.pool._store_function[object].append((self._name, store_field, fnct, tuple(fields2) if fields2 else None, order, length))
self.pool._store_function[object].sort(lambda x, y: cmp(x[4], y[4]))
for (key, _, msg) in self._sql_constraints:
self.pool._sql_error[self._table+'_'+key] = msg
@ -1047,6 +1054,7 @@ class BaseModel(object):
'name': n,
'model': self._name,
'res_id': r['id'],
'module': '__export__',
})
r = n
else:
@ -1730,13 +1738,69 @@ class BaseModel(object):
raise except_orm('View error', msg)
return arch, fields
def __get_default_calendar_view(self):
"""Generate a default calendar view (For internal use only).
"""
# TODO could return an etree instead of a string
def _get_default_form_view(self, cr, user, context=None):
""" Generates a default single-line form view using all fields
of the current model except the m2m and o2m ones.
arch = ('<?xml version="1.0" encoding="utf-8"?>\n'
'<calendar string="%s"') % (self._description)
:param cr: database cursor
:param int user: user id
:param dict context: connection context
:returns: a form view as an lxml document
:rtype: etree._Element
"""
view = etree.Element('form', string=self._description)
# TODO it seems fields_get can be replaced by _all_columns (no need for translation)
for field, descriptor in self.fields_get(cr, user, context=context).iteritems():
if descriptor['type'] in ('one2many', 'many2many'):
continue
etree.SubElement(view, 'field', name=field)
if descriptor['type'] == 'text':
etree.SubElement(view, 'newline')
return view
def _get_default_tree_view(self, cr, user, context=None):
""" Generates a single-field tree view, using _rec_name if
it's one of the columns or the first column it finds otherwise
:param cr: database cursor
:param int user: user id
:param dict context: connection context
:returns: a tree view as an lxml document
:rtype: etree._Element
"""
_rec_name = self._rec_name
if _rec_name not in self._columns:
_rec_name = self._columns.keys()[0]
view = etree.Element('tree', string=self._description)
etree.SubElement(view, 'field', name=_rec_name)
return view
def _get_default_calendar_view(self, cr, user, context=None):
""" Generates a default calendar view by trying to infer
calendar fields from a number of pre-set attribute names
:param cr: database cursor
:param int user: user id
:param dict context: connection context
:returns: a calendar view
:rtype: etree._Element
"""
def set_first_of(seq, in_, to):
"""Sets the first value of ``seq`` also found in ``in_`` to
the ``to`` attribute of the view being closed over.
Returns whether it's found a suitable value (and set it on
the attribute) or not
"""
for item in seq:
if item in in_:
view.set(to, item)
return True
return False
view = etree.Element('calendar', string=self._description)
etree.SubElement(view, 'field', name=self._rec_name)
if (self._date_name not in self._columns):
date_found = False
@ -1748,61 +1812,52 @@ class BaseModel(object):
if not date_found:
raise except_orm(_('Invalid Object Architecture!'), _("Insufficient fields for Calendar View!"))
view.set('date_start', self._date_name)
if self._date_name:
arch += ' date_start="%s"' % (self._date_name)
set_first_of(["user_id", "partner_id", "x_user_id", "x_partner_id"],
self._columns, 'color')
for color in ["user_id", "partner_id", "x_user_id", "x_partner_id"]:
if color in self._columns:
arch += ' color="' + color + '"'
break
if not set_first_of(["date_stop", "date_end", "x_date_stop", "x_date_end"],
self._columns, 'date_stop'):
if not set_first_of(["date_delay", "planned_hours", "x_date_delay", "x_planned_hours"],
self._columns, 'date_delay'):
raise except_orm(
_('Invalid Object Architecture!'),
_("Insufficient fields to generate a Calendar View for %s, missing a date_stop or a date_delay" % (self._name)))
dt_stop_flag = False
return view
for dt_stop in ["date_stop", "date_end", "x_date_stop", "x_date_end"]:
if dt_stop in self._columns:
arch += ' date_stop="' + dt_stop + '"'
dt_stop_flag = True
break
if not dt_stop_flag:
for dt_delay in ["date_delay", "planned_hours", "x_date_delay", "x_planned_hours"]:
if dt_delay in self._columns:
arch += ' date_delay="' + dt_delay + '"'
break
arch += ('>\n'
' <field name="%s"/>\n'
'</calendar>') % (self._rec_name)
return arch
def __get_default_search_view(self, cr, uid, context=None):
def _get_default_search_view(self, cr, uid, context=None):
"""
:param cr: database cursor
:param int user: user id
:param dict context: connection context
:returns: an lxml document of the view
:rtype: etree._Element
"""
form_view = self.fields_view_get(cr, uid, False, 'form', context=context)
tree_view = self.fields_view_get(cr, uid, False, 'tree', context=context)
fields_to_search = set()
# TODO it seems _all_columns could be used instead of fields_get (no need for translated fields info)
fields = self.fields_get(cr, uid, context=context)
for field in fields:
if fields[field].get('select'):
fields_to_search.add(field)
fields_to_search = set(
field for field, descriptor in fields.iteritems()
if descriptor.get('select'))
for view in (form_view, tree_view):
view_root = etree.fromstring(view['arch'])
# Only care about select=1 in xpath below, because select=2 is covered
# by the custom advanced search in clients
fields_to_search = fields_to_search.union(view_root.xpath("//field[@select=1]/@name"))
fields_to_search.update(view_root.xpath("//field[@select=1]/@name"))
tree_view_root = view_root # as provided by loop above
search_view = etree.Element("search", attrib={'string': tree_view_root.get("string", "")})
field_group = etree.Element("group")
search_view.append(field_group)
search_view = etree.Element("search", string=tree_view_root.get("string", ""))
field_group = etree.SubElement(search_view, "group")
for field_name in fields_to_search:
field_group.append(etree.Element("field", attrib={'name': field_name}))
etree.SubElement(field_group, "field", name=field_name)
#TODO tostring can be removed as fromstring is call directly after...
return etree.tostring(search_view, encoding="utf-8").replace('\t', '')
return search_view
#
# if view_id, view_type is not required
@ -1993,50 +2048,27 @@ class BaseModel(object):
# if a view was found
if sql_res:
result['type'] = sql_res['type']
result['view_id'] = sql_res['id']
source = etree.fromstring(encode(sql_res['arch']))
result['arch'] = apply_view_inheritance(cr, user, source, result['view_id'])
result['name'] = sql_res['name']
result['field_parent'] = sql_res['field_parent'] or False
result.update(
arch=apply_view_inheritance(cr, user, source, sql_res['id']),
type=sql_res['type'],
view_id=sql_res['id'],
name=sql_res['name'],
field_parent=sql_res['field_parent'] or False)
else:
# otherwise, build some kind of default view
if view_type == 'form':
# TODO it seems fields_get can be replaced by _all_columns (no need for translation)
res = self.fields_get(cr, user, context=context)
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,)
if res[x]['type'] == 'text':
xml += "<newline/>"
xml += "</form>"
elif view_type == 'tree':
_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, _rec_name)
elif view_type == 'calendar':
xml = self.__get_default_calendar_view()
elif view_type == 'search':
xml = self.__get_default_search_view(cr, user, context)
else:
try:
view = getattr(self, '_get_default_%s_view' % view_type)(
cr, user, context)
except AttributeError:
# 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
result.update(
arch=view,
name='default',
field_parent=False,
view_id=0)
if parent_view_model != self._name:
ctx = context.copy()
@ -2067,14 +2099,13 @@ class BaseModel(object):
resrelate = ir_values_obj.get(cr, user, 'action',
'client_action_relate', [(self._name, False)], False,
context)
resprint = map(clean, resprint)
resaction = map(clean, resaction)
if view_type != 'tree':
resaction = filter(lambda x: not x.get('multi'), resaction)
resprint = filter(lambda x: not x.get('multi'), resprint)
resaction = [clean(action) for action in resaction
if view_type == 'tree' or not action[2].get('multi')]
resprint = [clean(print_) for print_ in resprint
if view_type == 'tree' or not print_[2].get('multi')]
resrelate = map(lambda x: x[2], resrelate)
for x in resprint + resaction + resrelate:
for x in itertools.chain(resprint, resaction, resrelate):
x['string'] = x['name']
result['toolbar'] = {
@ -3459,7 +3490,7 @@ class BaseModel(object):
if uid == SUPERUSER_ID:
return
if self.is_transient:
if self.is_transient():
# Only one single implicit access rule for transient models: owner only!
# This is ok to hardcode because we assert that TransientModels always
# have log_access enabled and this the create_uid column is always there.
@ -4591,17 +4622,21 @@ class BaseModel(object):
return False
return True
def _get_xml_ids(self, cr, uid, ids, *args, **kwargs):
"""Find out the XML ID(s) of any database record.
def _get_external_ids(self, cr, uid, ids, *args, **kwargs):
"""Retrieve the External ID(s) of any database record.
**Synopsis**: ``_get_xml_ids(cr, uid, ids) -> { 'id': ['module.xml_id'] }``
:return: map of ids to the list of their fully qualified XML IDs
(empty list when there's none).
:return: map of ids to the list of their fully qualified External IDs
in the form ``module.key``, or an empty list when there's no External
ID for a record, e.g.::
{ 'id': ['module.ext_id', 'module.ext_id_bis'],
'id2': [] }
"""
model_data_obj = self.pool.get('ir.model.data')
data_ids = model_data_obj.search(cr, uid, [('model', '=', self._name), ('res_id', 'in', ids)])
data_results = model_data_obj.read(cr, uid, data_ids, ['module', 'name', 'res_id'])
ir_model_data = self.pool.get('ir.model.data')
data_ids = ir_model_data.search(cr, uid, [('model', '=', self._name), ('res_id', 'in', ids)])
data_results = ir_model_data.read(cr, uid, data_ids, ['module', 'name', 'res_id'])
result = {}
for id in ids:
# can't use dict.fromkeys() as the list would be shared!
@ -4610,29 +4645,35 @@ class BaseModel(object):
result[record['res_id']].append('%(module)s.%(name)s' % record)
return result
def get_xml_id(self, cr, uid, ids, *args, **kwargs):
"""Find out the XML ID of any database record, if there
def get_external_id(self, cr, uid, ids, *args, **kwargs):
"""Retrieve the External ID of any database record, if there
is one. This method works as a possible implementation
for a function field, to be able to add it to any
model object easily, referencing it as ``osv.osv.get_xml_id``.
model object easily, referencing it as ``Model.get_external_id``.
When multiple XML IDs exist for a record, only one
When multiple External IDs exist for a record, only one
of them is returned (randomly).
**Synopsis**: ``get_xml_id(cr, uid, ids) -> { 'id': 'module.xml_id' }``
:return: map of ids to their fully qualified XML ID,
defaulting to an empty string when there's none
(to be usable as a function field).
(to be usable as a function field),
e.g.::
{ 'id': 'module.ext_id',
'id2': '' }
"""
results = self._get_xml_ids(cr, uid, ids)
for k, v in results.items():
for k, v in results.iteritems():
if results[k]:
results[k] = v[0]
else:
results[k] = ''
return results
# backwards compatibility
get_xml_id = get_external_id
_get_xml_ids = _get_external_ids
# Transience
def is_transient(self):
""" Return whether the model is transient.

View File

@ -36,6 +36,5 @@ url = 'http://www.openerp.com'
author = 'OpenERP S.A.'
author_email = 'info@openerp.com'
license = 'AGPL-3'
timestamp = None
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -64,7 +64,7 @@ _SAFE_OPCODES = _EXPR_OPCODES.union(set(opmap[x] for x in [
'MAKE_FUNCTION', 'SLICE+0', 'SLICE+1', 'SLICE+2', 'SLICE+3',
# New in Python 2.7 - http://bugs.python.org/issue4715 :
'JUMP_IF_FALSE_OR_POP', 'JUMP_IF_TRUE_OR_POP', 'POP_JUMP_IF_FALSE',
'POP_JUMP_IF_TRUE'
'POP_JUMP_IF_TRUE', 'SETUP_EXCEPT', 'END_FINALLY'
] if x in opmap))
_logger = logging.getLogger('safe_eval')

View File

@ -340,6 +340,7 @@ class YamlInterpreter(object):
return record_dict
def process_ref(self, node, column=None):
assert node.search or node.id, '!ref node should have a `search` attribute or `id` attribute'
if node.search:
if node.model:
model_name = node.model
@ -377,7 +378,10 @@ class YamlInterpreter(object):
if column._type in ("many2many", "one2many"):
value = [(6, 0, elements)]
else: # many2one
value = self._get_first_result(elements)
if isinstance(elements, (list,tuple)):
value = self._get_first_result(elements)
else:
value = elements
elif column._type == "many2one":
value = self.get_id(expression)
elif column._type == "one2many":

View File

@ -60,7 +60,7 @@ def _eval_expr(cr, ident, workitem, action):
def execute_action(cr, ident, workitem, activity):
obj = pooler.get_pool(cr.dbname).get('ir.actions.server')
ctx = {'active_id':ident[2], 'active_ids':[ident[2]]}
ctx = {'active_model':ident[1], 'active_id':ident[2], 'active_ids':[ident[2]]}
result = obj.run(cr, ident[0], [activity['action_id']], ctx)
return result

View File

@ -63,8 +63,6 @@ def py2exe_options():
return {}
execfile(join(os.path.dirname(__file__), 'openerp', 'release.py'))
if timestamp:
version = version + "-" + timestamp
setuptools.setup(
name = 'openerp',