[MERGE] from trunk

bzr revid: rco@openerp.com-20120418100656-l0se3yr770qkg2yd
This commit is contained in:
Raphael Collet
2012-04-18 12:06:56 +02:00
24 changed files with 1748 additions and 988 deletions
+48
View File
@@ -0,0 +1,48 @@
Need action mechanism
=====================
ir.needaction mixin class
++++++++++++++++++++++++++
This revision adds a mixin class for objects using the need action feature.
Need action feature can be used by objects willing to be able to signal that an action is required on a particular record. If in the business logic an action must be performed by somebody, for instance validation by a manager, this mechanism allows to set a list of users asked to perform an action.
This class wraps a class (ir.needaction_users) that behaves like a many2many field. However, no field is added to the model inheriting from base.needaction. The mixin class manages the low-level considerations of updating relationships. Every change made on the record calls a method that updates the relationships.
Objects using the need_action feature should override the ``get_needaction_user_ids`` method. This methods returns a dictionary whose keys are record ids, and values a list of user ids, like in a many2many relationship. Therefore by defining only one method, you can specify if an action is required by defining the users that have to do it, in every possible situation.
This class also offers several global services,:
- ``needaction_get_record_ids``: for the current model and uid, get all record ids that ask this user to perform an action. This mechanism is used for instance to display the number of pending actions in menus, such as Leads (12)
- ``needaction_get_action_count``: as ``needaction_get_record_ids`` but returns only the number of action, not the ids (performs a search with count=True)
- ``needaction_get_user_record_references``: for a given uid, get all the records that ask this user to perform an action. Records are given as references, a list of tuples (model_name, record_id)
Menu modification
+++++++++++++++++
This revision adds three functional fields to ``ir.ui.menu`` model :
- ``uses_needaction``: boolean field. If the menu entry action is an act_window action, and if this action is related to a model that uses the need_action mechanism, this field is set to true. Otherwise, it is false.
- ``needaction_uid_ctr``: integer field. If the target model uses the need action mechanism, this field gives the number of actions the current user has to perform.
- ``needaction_record_ids``: many2many field. If the target model uses the need action mechanism, this field holds the ids of the record requesting the user to perform an action.
Those fields are functional, because they depend on the user and must therefore be computed at every refresh, each time menus are displayed. The use of the need action mechanism is done by taking into account the action domain in order to display accurate results. When computing the value of the functional fields, the ids of records asking the user to perform an action is concatenated to the action domain. A counting search is then performed on the model, giving back the number of action the users has to perform, limited to the domain of the action.
Addon implementation example
++++++++++++++++++++++++++++
In your ``foo`` module, you want to specify that when it is in state ``confirmed``, it has to be validated by a manager, given by the field ``manager_id``. After making ``foo`` inheriting from ``base.needaction``, you override the ``get_needaction_user_ids`` method:
::
[...]
_inherit = [base.needaction]
[...]
def get_needaction_user_ids(self, cr, uid, ids, context=None):
result = dict.fromkeys(ids)
for foo_obj in self.browse(cr, uid, ids, context=context):
# set the list void by default
result[foo_obj.id] = []
# if foo_obj is confirmed: manager is required to perform an action
if foo_obj.state == 'confirmed':
result[foo_obj.id] = [foo_obj.manager_id]
return result
+1
View File
@@ -14,3 +14,4 @@ New feature merges
:maxdepth: 1
api/user_img_specs
api/need_action_specs
-1
View File
@@ -58,7 +58,6 @@
'module/wizard/base_update_translations_view.xml',
'res/res_request_view.xml',
'res/res_lang_view.xml',
'res/res_log_view.xml',
'res/res_partner_report.xml',
'res/res_partner_view.xml',
'res/res_partner_shortcut_data.xml',
+4 -4
View File
@@ -85,7 +85,7 @@
<field name="name" readonly="1"/>
<newline/>
<group colspan="2" col="3">
<group col="2" colspan="2">
<group col="2" colspan="2" name="preferences">
<separator string="Preferences" colspan="2"/>
<field name="view" readonly="0"/>
<field name="context_lang" readonly="0"/>
@@ -94,7 +94,7 @@
</group>
<group col="2" colspan="1">
<separator string="Avatar" colspan="2"/>
<field name="avatar" widget='image' nolabel="1" colspan="2" on_change="onchange_avatar(avatar)"/>
<field name="avatar" widget='image' nolabel="1" colspan="2" on_change="onchange_avatar(avatar)" readonly="0"/>
</group>
</group>
<group name="default_filters" colspan="2" col="2">
@@ -128,11 +128,11 @@
<!-- Second nested group to avoid misalignment with email prefs groups
in simplified view -->
<group colspan="7" col="7">
<group col="2" colspan="1">
<group col="2" colspan="1" name="avatar">
<separator string="Avatar" colspan="2"/>
<field name="avatar" widget='image' nolabel="1" colspan="2" on_change="onchange_avatar(avatar)"/>
</group>
<group col="3" colspan="2">
<group col="3" colspan="2" name="preferences">
<separator string="Preferences" colspan="3"/>
<field name="context_lang"/>
<field name="context_tz"/>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+17 -17
View File
@@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.0\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-02-08 00:44+0000\n"
"PO-Revision-Date: 2012-01-31 16:45+0000\n"
"Last-Translator: Antony Lesuisse (OpenERP) <al@openerp.com>\n"
"PO-Revision-Date: 2012-04-03 15:31+0000\n"
"Last-Translator: tamnil <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: 2012-03-10 04:50+0000\n"
"X-Generator: Launchpad (build 14914)\n"
"X-Launchpad-Export-Date: 2012-04-04 04:40+0000\n"
"X-Generator: Launchpad (build 15055)\n"
#. module: base
#: model:res.country,name:base.sh
@@ -171,12 +171,12 @@ msgstr "Janela alvo"
#. module: base
#: model:ir.module.module,shortdesc:base.module_sale_analytic_plans
msgid "Sales Analytic Distribution"
msgstr ""
msgstr "Distribuição analítica de Vendas"
#. module: base
#: model:ir.module.module,shortdesc:base.module_web_process
msgid "Process"
msgstr ""
msgstr "Processo"
#. module: base
#: model:ir.module.module,shortdesc:base.module_analytic_journal_billing_rate
@@ -236,7 +236,7 @@ msgstr ""
#. module: base
#: model:ir.module.module,shortdesc:base.module_mrp_subproduct
msgid "MRP Subproducts"
msgstr ""
msgstr "Subprodutos MRP"
#. module: base
#: code:addons/base/module/module.py:390
@@ -269,7 +269,7 @@ msgstr ""
#: model:ir.module.category,name:base.module_category_sales_management
#: model:ir.module.module,shortdesc:base.module_sale
msgid "Sales Management"
msgstr ""
msgstr "Gestão de Vendas"
#. module: base
#: view:res.partner:0
@@ -375,7 +375,7 @@ msgstr ""
#. module: base
#: view:ir.module.module:0
msgid "Extra"
msgstr ""
msgstr "Extra"
#. module: base
#: code:addons/orm.py:2526
@@ -576,7 +576,7 @@ msgstr "Spanish (VE) / Español (VE)"
#. module: base
#: model:ir.module.module,shortdesc:base.module_hr_timesheet_invoice
msgid "Invoice on Timesheets"
msgstr ""
msgstr "Factura de Horários"
#. module: base
#: view:base.module.upgrade:0
@@ -887,7 +887,7 @@ msgstr ""
#. module: base
#: view:res.users:0
msgid "Email Preferences"
msgstr ""
msgstr "Preferências de Email"
#. module: base
#: model:ir.module.module,description:base.module_audittrail
@@ -1070,7 +1070,7 @@ msgstr "Tipo"
#. module: base
#: field:ir.mail_server,smtp_user:0
msgid "Username"
msgstr ""
msgstr "Nome de utilizador"
#. module: base
#: code:addons/orm.py:398
@@ -1243,7 +1243,7 @@ msgstr ""
#. module: base
#: model:ir.module.module,shortdesc:base.module_web_tests
msgid "Tests"
msgstr ""
msgstr "Testes"
#. module: base
#: field:ir.ui.view_sc,res_id:0
@@ -1916,7 +1916,7 @@ msgstr ""
#: code:addons/base/res/res_users.py:420
#, python-format
msgid "%s (copy)"
msgstr ""
msgstr "%s (cópia)"
#. module: base
#: model:ir.module.module,shortdesc:base.module_account_chart
@@ -2494,7 +2494,7 @@ msgstr "Nome do atalho"
#. module: base
#: help:ir.actions.act_window,limit:0
msgid "Default limit for the list view"
msgstr "Limite por defeito para a vista de lista"
msgstr "Limite por omissão para a vista de lista"
#. module: base
#: model:res.country,name:base.pg
@@ -10604,7 +10604,7 @@ msgstr "Exemplos"
#: field:ir.default,value:0
#: view:ir.values:0
msgid "Default Value"
msgstr "Valor por defeito"
msgstr "Valor por Omissão"
#. module: base
#: model:ir.model,name:base.model_res_country_state
@@ -11448,7 +11448,7 @@ msgstr ""
#: selection:ir.values,key:0
#: selection:res.partner.address,type:0
msgid "Default"
msgstr "Por defeito"
msgstr "Por omissão"
#. module: base
#: view:partner.wizard.ean.check:0
+1
View File
@@ -21,6 +21,7 @@
import ir_model
import ir_sequence
import ir_needaction
import ir_ui_menu
import ir_ui_view
import ir_default
+26 -2
View File
@@ -661,6 +661,30 @@
<menuitem action="ir_action_wizard" id="menu_ir_action_wizard" parent="base.next_id_6"/>
<!-- Needaction mechanism -->
<record model="ir.ui.view" id="view_ir_needaction_users_tree">
<field name="name">ir.needaction_users.tree</field>
<field name="model">ir.needaction_users</field>
<field name="type">tree</field>
<field name="sequence">10</field>
<field name="arch" type="xml">
<tree string="Subscription">
<field name="user_id"/>
<field name="res_model"/>
<field name="res_id"/>
</tree>
</field>
</record>
<record id="action_view_needaction_users" model="ir.actions.act_window">
<field name="name">Need action relationships</field>
<field name="res_model">ir.needaction_users</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="menu_needaction_users" name="Need actions" parent="base.menu_users" sequence="20" action="action_view_needaction_users" groups="base.group_no_one"/>
<!-- Companies -->
<menuitem id="menu_res_company_global"
parent="base.menu_administration"
@@ -2047,8 +2071,8 @@
<field name="search_view_id" ref="view_ir_mail_server_search"/>
</record>
<menuitem id="menu_email" name="Email" parent="base.menu_config" sequence="22" groups="base.group_extended"/>
<menuitem id="menu_mail_servers" parent="menu_email" action="action_ir_mail_server_list" sequence="15"/>
<menuitem id="menu_email" name="Email" parent="base.menu_config" sequence="22"/>
<menuitem id="menu_mail_servers" parent="menu_email" action="action_ir_mail_server_list" sequence="15" groups="base.group_no_one"/>
</data>
</openerp>
+2 -2
View File
@@ -717,8 +717,8 @@ class ir_model_data(osv.osv):
model_obj = self.pool.get(model)
if not context:
context = {}
# records created during module install should result in res.log entries that are already read!
context = dict(context, res_log_read=True)
# records created during module install should not display the messages of OpenChatter
context = dict(context, install_mode=True)
if xml_id and ('.' in xml_id):
assert len(xml_id.split('.'))==2, _("'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id") % (xml_id)
module, xml_id = xml_id.split('.')
+198
View File
@@ -0,0 +1,198 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2009-Today OpenERP SA (<http://www.openerp.com>)
#
# 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 openerp.pooler as pooler
from operator import itemgetter
from osv import osv, fields
from tools.translate import _
class ir_needaction_users(osv.osv):
'''
ir_needaction_users holds data related to the needaction
mechanism inside OpenERP. A needaction is characterized by:
- res_model: model of the record requiring an action
- res_id: ID of the record requiring an action
- user_id: foreign key to the res.users table, to the user that
has to perform the action
'''
_name = 'ir.needaction_users'
_description = 'Needaction relationship table'
_rec_name = 'id'
_order = 'id desc'
_columns = {
'res_model': fields.char('Related Document Model', size=128,
select=1, required=True),
'res_id': fields.integer('Related Document ID',
select=1, required=True),
'user_id': fields.many2one('res.users', string='Related User',
ondelete='cascade', select=1, required=True),
}
def _get_users(self, cr, uid, res_ids, res_model, context=None):
"""Given res_ids of res_model, get user_ids present in table"""
if context is None:
context = {}
needact_ids = self.search(cr, uid, [('res_model', '=', res_model), ('res_id', 'in', res_ids)], context=context)
return map(itemgetter('res_id'), self.read(cr, uid, needact_ids, context=context))
def create_users(self, cr, uid, res_ids, res_model, user_ids, context=None):
"""Given res_ids of res_model, add user_ids to the relationship table"""
if context is None:
context = {}
for res_id in res_ids:
for user_id in user_ids:
self.create(cr, uid, {'res_model': res_model, 'res_id': res_id, 'user_id': user_id}, context=context)
return True
def unlink_users(self, cr, uid, res_ids, res_model, context=None):
"""Given res_ids of res_model, delete all entries in the relationship table"""
if context is None:
context = {}
to_del_ids = self.search(cr, uid, [('res_model', '=', res_model), ('res_id', 'in', res_ids)], context=context)
return self.unlink(cr, uid, to_del_ids, context=context)
def update_users(self, cr, uid, res_ids, res_model, user_ids, context=None):
"""Given res_ids of res_model, update their entries in the relationship table to user_ids"""
# read current records
cur_users = self._get_users(cr, uid, res_ids, res_model, context=context)
if len(cur_users) == len(user_ids) and all([cur_user in user_ids for cur_user in cur_users]):
return True
# unlink old records
self.unlink_users(cr, uid, res_ids, res_model, context=context)
# link new records
self.create_users(cr, uid, res_ids, res_model, user_ids, context=context)
return True
class ir_needaction_mixin(osv.osv):
'''Mixin class for objects using the need action feature.
Need action feature can be used by objects willing to be able to
signal that an action is required on a particular record. If in the
business logic an action must be performed by somebody, for instance
validation by a manager, this mechanism allows to set a list of
users asked to perform an action.
This class wraps a class (ir.needaction_users) that behaves
like a many2many field. However, no field is added to the model
inheriting from base.needaction. The mixin class manages the low-level
considerations of updating relationships. Every change made on the
record calls a method that updates the relationships.
Objects using the need_action feature should override the
``get_needaction_user_ids`` method. This methods returns a dictionary
whose keys are record ids, and values a list of user ids, like
in a many2many relationship. Therefore by defining only one method,
you can specify if an action is required by defining the users
that have to do it, in every possible situation.
This class also offers several global services,:
- ``needaction_get_record_ids``: for the current model and uid, get
all record ids that ask this user to perform an action. This
mechanism is used for instance to display the number of pending
actions in menus, such as Leads (12)
- ``needaction_get_action_count``: as ``needaction_get_record_ids``
but returns only the number of action, not the ids (performs a
search with count=True)
- ``needaction_get_user_record_references``: for a given uid, get all
the records that ask this user to perform an action. Records
are given as references, a list of tuples (model_name, record_id)
'''
_name = 'ir.needaction_mixin'
_description = 'Need action of users on records API'
#------------------------------------------------------
# Addon API
#------------------------------------------------------
def get_needaction_user_ids(self, cr, uid, ids, context=None):
""" Returns the user_ids that have to perform an action
:return: dict { record_id: [user_ids], }
"""
return dict.fromkeys(ids, [])
def create(self, cr, uid, values, context=None):
if context is None:
context = {}
needact_table_obj = self.pool.get('ir.needaction_users')
# perform create
obj_id = super(ir_needaction_mixin, self).create(cr, uid, values, context=context)
# link user_ids
needaction_user_ids = self.get_needaction_user_ids(cr, uid, [obj_id], context=context)
needact_table_obj.create_users(cr, uid, [obj_id], self._name, needaction_user_ids[obj_id], context=context)
return obj_id
def write(self, cr, uid, ids, values, context=None):
if context is None:
context = {}
needact_table_obj = self.pool.get('ir.needaction_users')
# perform write
write_res = super(ir_needaction_mixin, self).write(cr, uid, ids, values, context=context)
# get and update user_ids
needaction_user_ids = self.get_needaction_user_ids(cr, uid, ids, context=context)
for id in ids:
needact_table_obj.update_users(cr, uid, [id], self._name, needaction_user_ids[id], context=context)
return write_res
def unlink(self, cr, uid, ids, context=None):
if context is None:
context = {}
# unlink user_ids
needact_table_obj = self.pool.get('ir.needaction_users')
needact_table_obj.unlink_users(cr, uid, ids, self._name, context=context)
# perform unlink
return super(ir_needaction_mixin, self).unlink(cr, uid, ids, context=context)
#------------------------------------------------------
# Need action API
#------------------------------------------------------
def needaction_get_record_ids(self, cr, uid, user_id, limit=80, context=None):
"""Given the current model and a user_id
get the number of actions it has to perform"""
if context is None:
context = {}
needact_table_obj = self.pool.get('ir.needaction_users')
needact_table_ids = needact_table_obj.search(cr, uid, [('res_model', '=', self._name), ('user_id', '=', user_id)], limit=limit, context=context)
return map(itemgetter('res_id'), needact_table_obj.read(cr, uid, needact_table_ids, context=context))
def needaction_get_action_count(self, cr, uid, user_id, limit=80, context=None):
"""Given the current model and a user_id
get the number of actions it has to perform"""
if context is None:
context = {}
needact_table_obj = self.pool.get('ir.needaction_users')
return needact_table_obj.search(cr, uid, [('res_model', '=', self._name), ('user_id', '=', user_id)], limit=limit, count=True, context=context)
def needaction_get_record_references(self, cr, uid, user_id, offset=None, limit=None, order=None, context=None):
"""For a given user_id, get all the records that asks this user to
perform an action. Records are given as references, a list of
tuples (model_name, record_id).
This method is trans-model."""
if context is None:
context = {}
needact_table_obj = self.pool.get('ir.needaction_users')
needact_table_ids = needact_table_obj.search(cr, uid, [('user_id', '=', user_id)], offset=offset, limit=limit, order=order, context=context)
needact_records = needact_table_obj.read(cr, uid, needact_table_ids, context=context)
return map(itemgetter('res_model', 'id'), needact_records)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+21
View File
@@ -255,6 +255,24 @@ class ir_ui_menu(osv.osv):
return res
def _get_needaction(self, cr, uid, ids, field_names, args, context=None):
if context is None:
context = {}
res = dict.fromkeys(ids)
for menu in self.browse(cr, uid, ids, context=context):
res[menu.id] = {}
if menu.action and menu.action.type == 'ir.actions.act_window' and menu.action.res_model:
menu_needaction_res = self.pool.get(menu.action.res_model).get_needaction_info(cr, uid, uid, domain=menu.action.domain, context=context)
# TODO: find the addon that causes a bug on runbot, not on local
if not isinstance(menu_needaction_res[1], (int, long)): menu_needaction_res[1] = 0
else:
menu_needaction_res = [False, 0, ()]
res[menu.id]['needaction_enabled'] = menu_needaction_res[0]
res[menu.id]['needaction_counter'] = menu_needaction_res[1]
# not used currently, therefore set to a void list
res[menu.id]['needaction_record_ids'] = []
return res
_columns = {
'name': fields.char('Menu', size=64, required=True, translate=True),
'sequence': fields.integer('Sequence'),
@@ -271,6 +289,9 @@ class ir_ui_menu(osv.osv):
'web_icon_hover':fields.char('Web Icon File (hover)', size=128),
'web_icon_data': fields.function(_get_image_icon, string='Web Icon Image', type='binary', readonly=True, store=True, multi='icon'),
'web_icon_hover_data':fields.function(_get_image_icon, string='Web Icon Image (hover)', type='binary', readonly=True, store=True, multi='icon'),
'needaction_enabled': fields.function(_get_needaction, string='Target model uses the need action mechanism', type='boolean', help='If the menu entry action is an act_window action, and if this action is related to a model that uses the need_action mechanism, this field is set to true. Otherwise, it is false.', multi='_get_needaction'),
'needaction_counter': fields.function(_get_needaction, string='Number of actions the user has to perform', type='integer', help='If the target model uses the need action mechanism, this field gives the number of actions the current user has to perform.', multi='_get_needaction'),
'needaction_record_ids': fields.function(_get_needaction, string='Ids of records requesting an action from the user', type='many2many', help='If the target model uses the need action mechanism, this field holds the ids of the record requesting the user to perform an action.', multi='_get_needaction'),
'action': fields.function(_action, fnct_inv=_action_inv,
type='reference', string='Action',
selection=[
+2
View File
@@ -76,6 +76,8 @@ class view(osv.osv):
def _check_xml(self, cr, uid, ids, context=None):
for view in self.browse(cr, uid, ids, context):
eview = etree.fromstring(view.arch.encode('utf8'))
if eview.get('layout') or eview.get('validate'):
continue
frng = tools.file_open(os.path.join('base','rng','view.rng'))
try:
relaxng_doc = etree.parse(frng)
@@ -187,21 +187,21 @@ class publisher_warranty_contract(osv.osv):
limit_date = (datetime.datetime.now() - _PREVIOUS_LOG_CHECK).strftime(misc.DEFAULT_SERVER_DATETIME_FORMAT)
for message in result["messages"]:
ids = self.pool.get("res.log").search(cr, uid, [("res_model", "=", "publisher_warranty.contract"),
("create_date", ">=", limit_date),
("name", "=", message)])
if ids:
continue
self.pool.get('res.log').create(cr, uid,
{
'name': message,
'res_model': "publisher_warranty.contract",
"read": True,
"user_id": False,
},
context=context
)
# old behavior based on res.log; now on mail.message, that is not necessarily installed
mail_message_obj = self.pool.get('mail.message')
if mail_message_obj:
for message in result["messages"]:
ids = mail_message_obj.search(cr, uid, [("model", "=", "publisher_warranty.contract"),
("create_date", ">=", limit_date),
("body_text", "=", message)])
if ids:
continue
mail_message_obj.create(cr, uid, {
'name': message,
'model': "publisher_warranty.contract",
'user_id': False,
}, context=context)
except Exception:
if cron_mode:
return False # we don't want to see any stack trace in cron
@@ -215,22 +215,15 @@ class publisher_warranty_contract(osv.osv):
@return: A list of html messages with ids, can be False or empty.
@rtype: list of tuples(int,string)
"""
ids = self.pool.get('res.log').search(cr, uid, [("res_model", "=", "publisher_warranty.contract")]
, order="create_date desc", limit=limit)
if not self.pool.get('mail.message'):
return []
ids = self.pool.get('mail.message').search(cr, uid, [("model", "=", "publisher_warranty.contract")]
, order="create_date desc", limit=limit, context=context)
if not ids:
return []
messages = [(x.id, x.name) for x in self.pool.get('res.log').browse(cr, uid, ids)]
messages = [(x.id, x.name) for x in self.pool.get('mail.message').browse(cr, uid, ids, context=context)]
return messages
def del_user_message(self, cr, uid, id, context=None):
"""
Delete a message.
"""
self.pool.get('res.log').unlink(cr, uid, [id])
return True
_columns = {
'name' : fields.char('Serial Key', size=384, required=True, help="Your OpenERP Publisher's Warranty Contract unique key, also called serial number."),
'date_start' : fields.date('Starting Date', readonly=True),
-1
View File
@@ -32,7 +32,6 @@ import res_company
import res_users
import res_request
import res_lang
import res_log
import res_widget
import ir_property
-1
View File
@@ -471,7 +471,6 @@ class res_config_settings(osv.osv_memory):
def default_get(self, cr, uid, fields, context=None):
ir_values = self.pool.get('ir.values')
ir_model_data = self.pool.get('ir.model.data')
classified = self._get_classified_fields(cr, uid, context)
res = super(res_config_settings, self).default_get(cr, uid, fields, context)
-80
View File
@@ -1,80 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 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/>.
#
##############################################################################
from osv import fields, osv
class res_log(osv.osv):
_name = 'res.log'
_columns = {
'name': fields.char('Message', size=250, help='The logging message.', required=True, select=1),
'user_id': fields.many2one('res.users','User'),
'res_model': fields.char('Object', size=128, select=1),
'context': fields.char('Context', size=250),
'res_id': fields.integer('Object ID'),
'secondary': fields.boolean('Secondary Log', help='Do not display this log if it belongs to the same object the user is working on'),
'create_date': fields.datetime('Creation Date', readonly=True, select=1),
'read': fields.boolean('Read', help="If this log item has been read, get() should not send it to the client"),
}
_defaults = {
'user_id': lambda self,cr,uid,ctx: uid,
'context': "{}",
'read': False,
}
_order='create_date desc'
_index_name = 'res_log_uid_read'
def _auto_init(self, cr, context=None):
super(res_log, self)._auto_init(cr, context)
cr.execute('SELECT 1 FROM pg_indexes WHERE indexname=%s',
(self._index_name,))
if not cr.fetchone():
cr.execute('CREATE INDEX %s ON res_log (user_id, read)' %
self._index_name)
def create(self, cr, uid, vals, context=None):
create_context = context and dict(context) or {}
if 'res_log_read' in create_context:
vals['read'] = create_context.pop('res_log_read')
if create_context and not vals.get('context'):
vals['context'] = create_context
return super(res_log, self).create(cr, uid, vals, context=context)
# TODO: do not return secondary log if same object than in the model (but unlink it)
def get(self, cr, uid, context=None):
unread_log_ids = self.search(cr, uid,
[('user_id','=',uid), ('read', '=', False)], context=context)
res = self.read(cr, uid, unread_log_ids,
['name','res_model','res_id','context'],
context=context)
res.reverse()
result = []
res_dict = {}
for r in res:
t = (r['res_model'], r['res_id'])
if t not in res_dict:
res_dict[t] = True
result.insert(0,r)
self.write(cr, uid, unread_log_ids, {'read': True}, context=context)
return result
res_log()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-65
View File
@@ -1,65 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="res_log_search" model="ir.ui.view">
<field name="name">res.log.search</field>
<field name="model">res.log</field>
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Logs">
<filter icon="terp-camera_test" string="Read" domain="[('read','=',True)]" name="read"/>
<filter icon="terp-gtk-jump-to-ltr" string="Unread" domain="[('read','&lt;&gt;',True)]" name="unread"/>
<separator orientation="vertical"/>
<filter icon="terp-personal" string="My Logs" domain="[('user_id','=',uid)]" name="my"/>
<filter icon="terp-stock_align_left_24" string="System Logs"
domain="[('res_model','=','publisher_warranty.contract')]" name="sys"/>
<separator orientation="vertical"/>
<field name="name"/>
<field name="user_id"/>
<field name="res_model"/>
<field name="create_date"/>
</search>
</field>
</record>
<record id="res_log_tree" model="ir.ui.view">
<field name="name">res.log.tree</field>
<field name="model">res.log</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Logs">
<field name="create_date"/>
<field name="user_id"/>
<field name="name"/>
</tree>
</field>
</record>
<record id="res_log_form" model="ir.ui.view">
<field name="name">res.log.form</field>
<field name="model">res.log</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Logs">
<group colspan="4" col="6">
<field name="name"/>
<field name="create_date"/>
<field name="read"/>
<field name="user_id" />
<field name="res_model"/>
<field name="res_id"/>
</group>
</form>
</field>
</record>
<record id="res_log_act_window" model="ir.actions.act_window">
<field name="name">Client Logs</field>
<field name="res_model">res.log</field>
<field name="view_type">form</field>
<field name="context">{'search_default_my': 1}</field>
</record>
<menuitem
action="res_log_act_window"
id="menu_res_log_act_window"
parent="base.menu_audit"/>
</data>
</openerp>
+6 -1
View File
@@ -307,10 +307,15 @@ class res_partner(osv.osv):
if name and operator in ('=', 'ilike', '=ilike', 'like'):
# search on the name of the contacts and of its company
name2 = operator == '=' and name or '%' + name + '%'
limit_str = ''
query_args = [name2]
if limit:
limit_str = ' limit %s'
query_args += [limit]
cr.execute('''SELECT partner.id FROM res_partner partner
LEFT JOIN res_partner company ON partner.parent_id = company.id
WHERE partner.name || ' (' || COALESCE(company.name,'') || ')'
''' + operator + ''' %s ''', (name2,))
''' + operator + ''' %s ''' + limit_str, query_args)
ids = map(lambda x: x[0], cr.fetchall())
if args:
ids = self.search(cr, uid, [('id', 'in', ids)] + args, limit=limit, context=context)
+64 -84
View File
@@ -331,27 +331,24 @@
<field name="model">res.partner</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Partners">
<form string="Partners" validate="0">
<group col="8" colspan="4">
<group col="4" colspan="4">
<field name="name" required="1"/>
<field name="title" size="0" groups="base.group_extended" domain="[('domain', '=', 'contact')]"/>
<newline/>
<field name="function" attrs="{'invisible': [('is_company', '=', True)]}" colspan="4"/>
<field name="parent_id" string="Company" colspan="4" attrs="{'invisible': [('is_company','=', True)]}"
domain="[('is_company', '=', True)]" context="{'default_is_company': True}"
on_change="onchange_address(use_parent_address, parent_id)"/>
<group col="2">
<field name="photo" widget='image' nolabel="1"/>
</group>
<group colspan="4">
<h2><field name="name" required="1" nolabel="1" colspan="4"/></h2><newline/>
<field name="function" attrs="{'invisible': [('is_company', '=', True)]}"/>
<field name="title" size="0" groups="base.group_extended" domain="[('domain', '=', 'contact')]"/>
<field name="parent_id" string="Company" attrs="{'invisible': [('is_company','=', True)]}"
domain="[('is_company', '=', True)]" context="{'default_is_company': True}"
on_change="onchange_address(use_parent_address, parent_id)"/>
</group>
<group col="2">
<field name="is_company" on_change="onchange_type(is_company)"/>
</group>
<group col="2">
<field name="customer"/>
<field name="supplier"/>
</group>
<group col="2">
<field name="photo" widget='image' nolabel="1"/>
</group>
</group>
<notebook colspan="4">
<page string="General">
@@ -372,7 +369,6 @@
</group>
<group colspan="2">
<separator string="Communication" colspan="4"/>
<field name="lang" colspan="4"/>
<field name="phone" colspan="4"/>
<field name="mobile" colspan="4"/>
<field name="fax" colspan="4"/>
@@ -381,77 +377,61 @@
<field name="ref" groups="base.group_extended" colspan="4"/>
</group>
<group colspan="4" attrs="{'invisible': [('is_company','=', False)]}">
<field name="child_ids" context="{'default_parent_id': active_id}" nolabel="1">
<form string="Partners">
<group col="8" colspan="4">
<group col="4" colspan="4">
<field name="name" required="1"/>
<field name="title" size="0" groups="base.group_extended" domain="[('domain', '=', 'contact')]"/>
<newline/>
<field name="function" attrs="{'invisible': [('is_company', '=', True)]}" colspan="4"/>
<field name="parent_id" string="Company" colspan="4" attrs="{'invisible': [('is_company','=', True)]}"
domain="[('is_company', '=', True)]" context="{'default_is_company': True}"
on_change="onchange_address(use_parent_address, parent_id)" invisible="1"/>
</group>
<group col="2">
<field name="is_company" on_change="onchange_type(is_company)" invisible="1"/>
</group>
<group col="2">
<field name="customer"/>
<field name="supplier"/>
</group>
<group col="2">
<field name="photo" widget='image' nolabel="1"/>
</group>
</group>
<notebook colspan="4">
<page string="General">
<group colspan="2">
<separator string="Address" colspan="4"/>
<field name="type" string="Type" attrs="{'invisible': [('is_company','=', True)]}"/>
<group colspan="2">
<field name="use_parent_address" attrs="{'invisible': [('is_company','=', True)]}" on_change="onchange_address(use_parent_address, parent_id)"/>
</group>
<newline/>
<field name="street" colspan="4"/>
<field name="street2" colspan="4"/>
<field name="zip"/>
<field name="city"/>
<field name="country_id"/>
<field name="state_id"/>
</group>
<group colspan="2">
<separator string="Communication" colspan="4"/>
<field name="lang" colspan="4"/>
<field name="phone" colspan="4"/>
<field name="mobile" colspan="4"/>
<field name="fax" colspan="4"/>
<field name="email" widget="email" colspan="4"/>
<field name="website" widget="url" colspan="4"/>
<field name="ref" groups="base.group_extended" colspan="4"/>
</group>
</page>
<page string="Sales &amp; Purchases" attrs="{'invisible': [('customer', '=', False), ('supplier', '=', False)]}">
<separator string="General Information" colspan="4"/>
<field name="user_id"/>
<field name="active" groups="base.group_extended"/>
<field name="date"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
<newline/>
</page>
<page string="Categories" groups="base.group_extended">
<field name="category_id" colspan="4" nolabel="1"/>
</page>
<page string="Notes">
<field name="comment" colspan="4" nolabel="1"/>
</page>
</notebook>
</form>
</field>
<field name="child_ids" context="{'default_parent_id': active_id}" nolabel="1" mode="kanban,list">
<kanban>
<field name="color"/>
<field name="name"/>
<field name="title"/>
<field name="email"/>
<field name="parent_id"/>
<field name="is_company"/>
<field name="function"/>
<field name="phone"/>
<field name="street"/>
<field name="street2"/>
<field name="photo"/>
<field name="zip"/>
<field name="city"/>
<field name="country_id"/>
<field name="mobile"/>
<field name="state_id"/>
<templates>
<t t-name="kanban-box">
<t t-set="color" t-value="kanban_color(record.color.raw_value)"/>
<div t-att-class="color + (record.title.raw_value == 1 ? ' oe_kanban_color_alert' : '')">
<div class="oe_module_vignette">
<a type="edit">
<img t-att-src="kanban_image('res.partner', 'photo', record.id.value)" class="oe_form_avatar oe_kanban_avatar_toto"/>
</a>
<div class="oe_module_desc">
<div class="oe_kanban_box_content oe_kanban_color_bglight oe_kanban_box_show_onclick_trigger oe_kanban_color_border">
<table class="oe_kanban_table">
<tr>
<td class="oe_kanban_title1" align="left" valign="middle">
<h4><a type="edit"><field name="name"/></a></h4>
<i><div t-if="record.function.raw_value">
<field name="function"/><br/></div></i>
</td>
<td valign="top" align="right">
<a t-if="record.email.raw_value" title="Mail" t-att-href="'mailto:'+record.email.value" style="text-decoration: none;" >
<img src="/web/static/src/img/icons/terp-mail-message-new.png" border="0" width="16" height="16"/>
</a>
</td>
</tr>
</table>
</div>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</group>
</page>
<page string="Sales &amp; Purchases" attrs="{'invisible': [('customer', '=', False), ('supplier', '=', False)]}">
<separator string="General Information" colspan="4"/>
<field name="lang" colspan="4"/>
<field name="user_id"/>
<field name="active" groups="base.group_extended"/>
<field name="date"/>
@@ -533,7 +513,7 @@
<img t-att-src="kanban_image('res.partner', 'photo', record.id.value)" width="64" height="64" class="oe_module_icon"/>
</a>
<div class="oe_module_desc">
<div class="oe_kanban_box_content oe_kanban_color_bglight oe_kanban_box_show_onclick_trigger oe_kanban_color_border">
<div class="oe_kanban_box_content oe_kanban_box_show_onclick_trigger oe_kanban_color_border">
<table class="oe_kanban_table">
<tr>
<td class="oe_kanban_title1" align="left" valign="middle">
@@ -541,7 +521,7 @@
<div t-if="record.parent_id.raw_value"><field name="parent_id"/></div>
</h4>
<i><div t-if="record.contact_address.raw_value"><field name="contact_address"/><br/></div>
<div t-if="record.email.raw_value">
<div t-if="record.email.raw_value">
<field name="email"/><br/></div>
<div t-if="record.mobile.raw_value">
<field name="mobile"/><br/>
+2 -3
View File
@@ -40,7 +40,6 @@ from service import security
import tools
from tools.translate import _
_logger = logging.getLogger(__name__)
class groups(osv.osv):
@@ -205,7 +204,7 @@ class users(osv.osv):
img_stream = StringIO.StringIO()
img.save(img_stream, "PNG")
return img_stream.getvalue().encode('base64')
def _get_avatar(self, cr, uid, ids, name, args, context=None):
result = dict.fromkeys(ids, False)
for user in self.browse(cr, uid, ids, context=context):
@@ -376,7 +375,7 @@ class users(osv.osv):
}
# User can write to a few of her own fields (but not her groups for example)
SELF_WRITEABLE_FIELDS = ['menu_tips','view', 'password', 'signature', 'action_id', 'company_id', 'user_email', 'name']
SELF_WRITEABLE_FIELDS = ['menu_tips','view', 'password', 'signature', 'action_id', 'company_id', 'user_email', 'name', 'avatar', 'avatar_big']
def write(self, cr, uid, ids, values, context=None):
if not hasattr(ids, '__iter__'):
@@ -117,8 +117,10 @@
"access_ir_filters","ir_filters_all","model_ir_filters",,1,1,1,1
"access_res_widget","res.widget","model_res_widget","group_erp_manager",1,1,1,1
"access_res_widget_user","res.widget.user","model_res_widget",,1,0,0,0
"access_res_log_all","res.log","model_res_log",,1,1,1,1
"access_ir_config_parameter","ir_config_parameter","model_ir_config_parameter",,1,0,0,0
"access_ir_mail_server_all","ir_mail_server","model_ir_mail_server",,1,0,0,0
"access_ir_actions_todo_category","ir_actions_todo_category","model_ir_actions_todo_category","group_system",1,1,1,1
"access_ir_actions_client","ir_actions_client all","model_ir_actions_client",,1,0,0,0
"access_ir_needaction_users","ir_needaction_users","model_ir_needaction_users",,1,1,1,1
"access_ir_needaction_mixin","ir_needaction_mixin","model_ir_needaction_mixin",,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
117 access_ir_filters ir_filters_all model_ir_filters 1 1 1 1
118 access_res_widget res.widget model_res_widget group_erp_manager 1 1 1 1
119 access_res_widget_user res.widget.user model_res_widget 1 0 0 0
access_res_log_all res.log model_res_log 1 1 1 1
120 access_ir_config_parameter ir_config_parameter model_ir_config_parameter 1 0 0 0
121 access_ir_mail_server_all ir_mail_server model_ir_mail_server 1 0 0 0
122 access_ir_actions_todo_category ir_actions_todo_category model_ir_actions_todo_category group_system 1 1 1 1
123 access_ir_actions_client ir_actions_client all model_ir_actions_client 1 0 0 0
124 access_ir_needaction_users ir_needaction_users model_ir_needaction_users 1 1 1 1
125 access_ir_needaction_mixin ir_needaction_mixin model_ir_needaction_mixin 1 1 1 1
126
+1 -4
View File
@@ -212,10 +212,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
delattr(package, kind)
cr.commit()
# mark new res_log records as read
cr.execute("update res_log set read=True where create_date >= %s", (dt_before_load,))
cr.commit()
return loaded_modules, processed_modules
+32 -14
View File
@@ -709,18 +709,8 @@ class BaseModel(object):
CONCURRENCY_CHECK_FIELD = '__last_update'
def log(self, cr, uid, id, message, secondary=False, context=None):
if context and context.get('disable_log'):
return True
return self.pool.get('res.log').create(cr, uid,
{
'name': message,
'res_model': self._name,
'secondary': secondary,
'res_id': id,
},
context=context
)
return _logger.warning("log() is deprecated. Please use OpenChatter notification system instead of the res.log mechanism.")
def view_init(self, cr, uid, fields_list, context=None):
"""Override this method to do specific things when a view on the object is opened."""
pass
@@ -1661,7 +1651,7 @@ class BaseModel(object):
children = False
views = {}
for f in node:
if f.tag in ('form', 'tree', 'graph'):
if f.tag in ('form', 'tree', 'graph', 'kanban'):
node.remove(f)
ctx = context.copy()
ctx['base_model_name'] = self._name
@@ -4875,7 +4865,35 @@ class BaseModel(object):
# backwards compatibility
get_xml_id = get_external_id
_get_xml_ids = _get_external_ids
def get_needaction_info(self, cr, uid, user_id, limit=None, order=None, domain=False, context=None):
"""Base method for needaction mechanism
- see ir.needaction for actual implementation
- if the model uses the need action mechanism
(hasattr(model_obj, 'needaction_get_record_ids')):
- get the record ids on which the user has actions to perform
- evaluate the menu domain
- compose a new domain: menu domain, limited to ids of
records requesting an action
- count the number of records maching that domain, that
is the number of actions the user has to perform
- this method returns default values
:param: model_name: the name of the model (ex: hr.holidays)
:param: user_id: the id of user
:return: [uses_needaction=True/False, needaction_uid_ctr=%d]
"""
if hasattr(self, 'needaction_get_record_ids'):
ids = self.needaction_get_record_ids(cr, uid, user_id, limit=8192, context=context)
if not ids:
return [True, 0, []]
if domain:
new_domain = eval(domain, locals_dict={'uid': user_id}) + [('id', 'in', ids)]
else:
new_domain = [('id', 'in', ids)]
return [True, self.search(cr, uid, new_domain, limit=limit, order=order, count=True, context=context), ids]
else:
return [False, 0, []]
# Transience
def is_transient(self):
""" Return whether the model is transient.