merge trunk
bzr revid: nicolas.vanhoren@openerp.com-20130108090624-cubi563m34f1j6ab
This commit is contained in:
@@ -17,3 +17,5 @@ include/
|
||||
lib/
|
||||
share/
|
||||
doc/_build/*
|
||||
win32/*.bat
|
||||
win32/meta.py
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
print '-' * 70
|
||||
print "DEPRECATED: you are starting the OpenERP server with its old path,"
|
||||
print "please use the new executable (available in the parent directory)."
|
||||
print '-' * 70
|
||||
|
||||
# Change to the parent directory ...
|
||||
os.chdir(os.path.normpath(os.path.dirname(__file__)))
|
||||
os.chdir('..')
|
||||
# ... and execute the new executable.
|
||||
os.execv('openerp-server', sys.argv)
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
Vendored
+3
@@ -19,10 +19,12 @@ Depends:
|
||||
python-docutils,
|
||||
python-feedparser,
|
||||
python-gdata,
|
||||
python-jinja2,
|
||||
python-ldap,
|
||||
python-libxslt1,
|
||||
python-lxml,
|
||||
python-mako,
|
||||
python-mock,
|
||||
python-openid,
|
||||
python-psutil,
|
||||
python-psycopg2,
|
||||
@@ -33,6 +35,7 @@ Depends:
|
||||
python-reportlab,
|
||||
python-simplejson,
|
||||
python-tz,
|
||||
python-unittest2,
|
||||
python-vatnumber,
|
||||
python-vobject,
|
||||
python-webdav,
|
||||
|
||||
@@ -135,6 +135,9 @@ The view describes how the edition form or the data tree/list appear on screen.
|
||||
|
||||
A form can be called by an action opening in 'Tree' mode. The form view is generally opened from the list mode (like if the user pushes on 'switch view').
|
||||
|
||||
.. _domain:
|
||||
.. _domains:
|
||||
|
||||
The domain
|
||||
----------
|
||||
|
||||
|
||||
@@ -141,10 +141,6 @@ for users who do not belong to the authorized groups:
|
||||
|
||||
.. note:: The tests related to this feature are in ``openerp/tests/test_acl.py``.
|
||||
|
||||
.. warning:: At the time of writing the implementation of this feature is partial
|
||||
and does not yet restrict read/write RPC access to the field.
|
||||
The corresponding test is written already but currently disabled.
|
||||
|
||||
Workflow transition rules
|
||||
+++++++++++++++++++++++++
|
||||
|
||||
|
||||
@@ -10,3 +10,4 @@ Miscellanous
|
||||
06_misc_need_action_specs.rst
|
||||
06_misc_user_img_specs.rst
|
||||
06_misc_import.rst
|
||||
06_misc_auto_join.rst
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
.. _performing_joins_in_select:
|
||||
|
||||
Perfoming joins in select
|
||||
=========================
|
||||
|
||||
.. versionadded:: 7.0
|
||||
|
||||
Starting with OpenERP 7.0, an ``auto_join`` attribute is added on *many2one* and
|
||||
*one2many* fields. The purpose is to allow the automatic generation of joins in
|
||||
select queries. This attribute is set to False by default, therefore not changing
|
||||
the default behavior. Please note that we consider this feature as still experimental
|
||||
and should be used only if you understand its limitations and targets.
|
||||
|
||||
Without ``_auto_join``, the behavior of expression.parse() is the same as before.
|
||||
Leafs holding a path beginning with many2one or one2many fields perform a search
|
||||
on the relational table. The result is then used to replace the leaf content.
|
||||
For example, if you have on res.partner a domain like ``[('bank_ids.name',
|
||||
'like', 'foo')]`` with bank_ids linking to res.partner.bank, 3 queries will be
|
||||
performed :
|
||||
|
||||
- 1 on res_partner_bank, with domain ``[('name', '=', 'foo')]``, that returns a
|
||||
list of res.partner.bank ids (bids)
|
||||
- 1 on res_partner, with a domain ``['bank_ids', 'in', bids)]``, that returns a
|
||||
list of res.partner ids (pids)
|
||||
- 1 on res_partner, with a domain ``[('id', 'in', pids)]``
|
||||
|
||||
When the ``auto_join`` attribute is True on a relational field, the destination
|
||||
table will be joined to produce only one query.
|
||||
|
||||
- the relational table is accessed using an alias: ``'"res_partner_bank"
|
||||
as res_partner__bank_ids``. The alias is generated using the relational field
|
||||
name. This allows to have multiple joins with different join conditions on the
|
||||
same table, depending on the domain.
|
||||
- there is a join condition between the destination table and the main table:
|
||||
``res_partner__bank_ids."partner_id"=res_partner."id"``
|
||||
- the condition is then written on the relational table:
|
||||
``res_partner__bank_ids."name" = 'foo'``
|
||||
|
||||
This manipulation is performed in expression.parse(). It checks leafs that
|
||||
contain a path, i.e. any domain containing a '.'. It then checks whether the
|
||||
first item of the path is a *many2one* or *one2many* field with the ``auto_join``
|
||||
attribute set. If set, it adds a join query and recursively analyzes the
|
||||
remaining of the leaf, using the same behavior. If the remaining path also holds
|
||||
a path with auto_join fields, it will add all tables and add every necessary
|
||||
join conditions.
|
||||
|
||||
Chaining joins allows to reduce the number of queries performed, and to avoid
|
||||
having too long equivalent leaf replacement in domains. Indeed, the internal
|
||||
queries produced by this behavior can be very costly, because they were generally
|
||||
select queries without limit that could lead to huge ('id', 'in', [...])
|
||||
leafs to analyze and execute.
|
||||
|
||||
Some limitations exist on this feature that limits its current use as of version
|
||||
7.0. **This feature is therefore considered as experimental, and used
|
||||
to speedup some precise bottlenecks in OpenERP**.
|
||||
|
||||
List of known issues and limitations:
|
||||
|
||||
- using ``auto_join`` bypasses the business logic; no name search is performed,
|
||||
only direct matches between ids using join conditions
|
||||
- ir.rules are not taken into account when analyzing and adding the join
|
||||
conditions
|
||||
|
||||
List of already-supported corner cases :
|
||||
|
||||
- one2many fields having a domain attribute. Static domains as well as dynamic
|
||||
domain are supported
|
||||
- auto_join leading to functional searchable fields
|
||||
|
||||
Typical use in OpenERP 7.0:
|
||||
|
||||
- in mail module: notification_ids field on mail_message, allowing to speedup
|
||||
the display of the various mailboxes
|
||||
- in mail module: message_ids field on mail_thread, allowing to speedup the
|
||||
display of needaction counters and documents having unread messages
|
||||
+11
-11
@@ -76,9 +76,9 @@ This phase also generates the ``rows`` indexes for any
|
||||
Conversion
|
||||
++++++++++
|
||||
|
||||
This second phase takes the record dicts, extracts the :ref:`dbid` and
|
||||
:ref:`xid` if present and attempts to convert each field to a type
|
||||
matching what OpenERP expects to write.
|
||||
This second phase takes the record dicts, extracts the :term:`database
|
||||
ID` and :term:`external ID` if present and attempts to convert each
|
||||
field to a type matching what OpenERP expects to write.
|
||||
|
||||
* Empty fields (empty strings) are replaced with the ``False`` value
|
||||
|
||||
@@ -141,14 +141,14 @@ If ``name_search`` finds no value, an error is generated. If
|
||||
``name_search`` finds multiple value, a warning is generated to warn
|
||||
the user of ``name_search`` collisions.
|
||||
|
||||
If the specified field is a :ref:`xid` (``m2o/id``), the
|
||||
If the specified field is a :term:`external ID` (``m2o/id``), the
|
||||
corresponding record it looked up in the database and used as the
|
||||
field's value. If no record is found matching the provided external
|
||||
ID, an error is generated.
|
||||
|
||||
If the specified field is a :ref:`dbid` (``m2o/.id``), the process is
|
||||
the same as for external ids (on database identifiers instead of
|
||||
external ones).
|
||||
If the specified field is a :term:`database ID` (``m2o/.id``), the
|
||||
process is the same as for external ids (on database identifiers
|
||||
instead of external ones).
|
||||
|
||||
Many to Many field
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
@@ -161,11 +161,11 @@ One to Many field
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
For each o2m record extracted, if the record has a ``name``,
|
||||
:ref:`xid` or :ref:`dbid` the :ref:`dbid` is looked up and checked
|
||||
through the same process as for m2o fields.
|
||||
:term:`external ID` or :term:`database ID` the :term:`database ID` is
|
||||
looked up and checked through the same process as for m2o fields.
|
||||
|
||||
If a :ref:`dbid` was found, a LINK_TO command is emmitted, followed by
|
||||
an UPDATE with the non-db values for the relational field.
|
||||
If a :term:`database ID` was found, a LINK_TO command is emmitted,
|
||||
followed by an UPDATE with the non-db values for the relational field.
|
||||
|
||||
Otherwise a CREATE command is emmitted.
|
||||
|
||||
|
||||
@@ -27,5 +27,15 @@ OpenERP Server API
|
||||
api_core.rst
|
||||
api_models.rst
|
||||
|
||||
Concepts
|
||||
''''''''
|
||||
|
||||
.. glossary::
|
||||
|
||||
Database ID
|
||||
|
||||
The primary key of a record in a PostgreSQL table (or a
|
||||
virtual version thereof), usually varies from one database to
|
||||
the next.
|
||||
|
||||
External ID
|
||||
|
||||
@@ -40,7 +40,6 @@ import service
|
||||
import sql_db
|
||||
import test
|
||||
import tools
|
||||
import wizard
|
||||
import workflow
|
||||
# backward compatilbility
|
||||
# TODO: This is for the web addons, can be removed later.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
# Copyright (C) 2010 OpenERP s.a. (<http://openerp.com>).
|
||||
# Copyright (C) 2010-2012 OpenERP s.a. (<http://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
|
||||
@@ -80,12 +80,12 @@ The kernel of OpenERP, needed for all installation.
|
||||
'res/res_bank_view.xml',
|
||||
'res/res_country_view.xml',
|
||||
'res/res_currency_view.xml',
|
||||
'res/wizard/change_password_wizard_view.xml',
|
||||
'res/res_users_view.xml',
|
||||
'res/res_partner_data.xml',
|
||||
'res/ir_property_view.xml',
|
||||
'security/base_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'security/ir.model.access-1.csv', # res.partner.address is deprecated; it is still there for backward compability only and will be removed in next version
|
||||
],
|
||||
'demo': [
|
||||
'base_demo.xml',
|
||||
@@ -94,19 +94,17 @@ The kernel of OpenERP, needed for all installation.
|
||||
'res/res_partner_image_demo.xml',
|
||||
],
|
||||
'test': [
|
||||
'test/base_test.xml',
|
||||
'test/base_test.yml',
|
||||
'test/test_context.xml',
|
||||
'test/bug_lp541545.xml',
|
||||
'test/test_osv_expression.yml',
|
||||
'test/test_ir_rule.yml', # <-- These tests modify/add/delete ir_rules.
|
||||
# Commented because this takes some time.
|
||||
# This must be (un)commented with the corresponding import statement
|
||||
# in test/__init__.py.
|
||||
# 'test/test_ir_cron.yml', # <-- These tests perform a roolback.
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': True,
|
||||
'css': ['static/src/css/modules.css'],
|
||||
'js': [
|
||||
'static/src/js/apps.js',
|
||||
],
|
||||
}
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -149,7 +149,6 @@ CREATE TABLE res_users (
|
||||
active boolean default True,
|
||||
login varchar(64) NOT NULL UNIQUE,
|
||||
password varchar(64) default null,
|
||||
lang varchar(64) default '',
|
||||
-- No FK references below, will be added later by ORM
|
||||
-- (when the destination rows exist)
|
||||
company_id int,
|
||||
@@ -316,13 +315,29 @@ CREATE TABLE ir_module_module_dependency (
|
||||
primary key(id)
|
||||
);
|
||||
|
||||
CREATE TABLE res_company (
|
||||
CREATE TABLE res_partner (
|
||||
id serial NOT NULL,
|
||||
name character varying(64) not null,
|
||||
parent_id integer references res_company on delete set null,
|
||||
name character varying(128),
|
||||
lang varchar(64),
|
||||
company_id int,
|
||||
primary key(id)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE res_currency (
|
||||
id serial PRIMARY KEY,
|
||||
name VARCHAR(32) NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE res_company (
|
||||
id serial PRIMARY KEY,
|
||||
name character varying(128) not null,
|
||||
parent_id integer references res_company on delete set null,
|
||||
partner_id integer not null references res_partner,
|
||||
currency_id integer not null references res_currency
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE res_lang (
|
||||
id serial PRIMARY KEY,
|
||||
name VARCHAR(64) NOT NULL UNIQUE,
|
||||
@@ -375,16 +390,24 @@ CREATE TABLE ir_model_relation (
|
||||
module integer NOT NULL references ir_module_module on delete restrict,
|
||||
model integer NOT NULL references ir_model on delete restrict,
|
||||
name character varying(128) NOT NULL
|
||||
);
|
||||
);
|
||||
|
||||
---------------------------------
|
||||
-- Users
|
||||
---------------------------------
|
||||
insert into res_users (id,login,password,active,company_id,partner_id) VALUES (1,'admin','admin',true,1,1);
|
||||
insert into ir_model_data (name,module,model,noupdate,res_id) VALUES ('user_root','base','res.users',true,1);
|
||||
|
||||
insert into res_users (id,login,password,active,company_id,partner_id,lang) values (1,'admin','admin',True,1,1,'en_US');
|
||||
insert into ir_model_data (name,module,model,noupdate,res_id) values ('user_root','base','res.users',True,1);
|
||||
insert into res_partner (id, name, lang, company_id) VALUES (1, 'Your Company', 'en_US', 1);
|
||||
insert into ir_model_data (name,module,model,noupdate,res_id) VALUES ('main_partner','base','res.partner',true,1);
|
||||
|
||||
-- Compatibility purpose, to remove V6.0
|
||||
insert into ir_model_data (name,module,model,noupdate,res_id) values ('user_admin','base','res.users',True,1);
|
||||
insert into res_currency (id, name) VALUES (1, 'EUR');
|
||||
insert into ir_model_data (name,module,model,noupdate,res_id) VALUES ('EUR','base','res.currency',true,1);
|
||||
|
||||
insert into res_company (id, name, partner_id, currency_id) VALUES (1, 'Your Company', 1, 1);
|
||||
insert into ir_model_data (name,module,model,noupdate,res_id) VALUES ('main_company','base','res.company',true,1);
|
||||
|
||||
select setval('res_company_id_seq', 2);
|
||||
select setval('res_users_id_seq', 2);
|
||||
select setval('res_partner_id_seq', 2);
|
||||
select setval('res_currency_id_seq', 2);
|
||||
@@ -32,6 +32,7 @@
|
||||
<record id="main_partner" model="res.partner" context="{'default_is_company': True}">
|
||||
<field name="name">Your Company</field>
|
||||
<field name="company_id" eval="None"/>
|
||||
<field name="image" eval="False"/>
|
||||
<field name="customer" eval="False"/>
|
||||
<field name="is_company" eval="True"/>
|
||||
<field name="street"></field>
|
||||
@@ -61,7 +62,7 @@
|
||||
<record id="main_company" model="res.company">
|
||||
<field name="name">Your Company</field>
|
||||
<field name="partner_id" ref="main_partner"/>
|
||||
<field name="rml_header1">Your Company Slogan</field>
|
||||
<field name="rml_header1">Your Company Tagline</field>
|
||||
<field name="currency_id" ref="base.EUR"/>
|
||||
</record>
|
||||
|
||||
@@ -88,6 +89,13 @@ Administrator</field>
|
||||
<record id="EUR" model="res.currency">
|
||||
<field name="company_id" ref="main_company"/>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="ir_mail_server_localhost0" model="ir.mail_server">
|
||||
<field name="name">localhost</field>
|
||||
<field name="smtp_host">localhost</field>
|
||||
<field eval="25" name="smtp_port"/>
|
||||
<field eval="10" name="sequence"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
@@ -7,6 +7,74 @@
|
||||
<field name="customer" eval="False"/>
|
||||
<field name="email">demo@example.com</field>
|
||||
</record>
|
||||
|
||||
<record id="main_partner" model="res.partner">
|
||||
<field name="image">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
|
||||
BZrmED0AAAAASUVORK5CYII=
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="user_demo" model="res.users">
|
||||
<field name="partner_id" ref="base.partner_demo"/>
|
||||
<field name="login">demo</field>
|
||||
@@ -14,7 +82,7 @@
|
||||
<field name="signature">--
|
||||
Mr Demo</field>
|
||||
<field name="company_id" ref="main_company"/>
|
||||
<field name="groups_id" eval="[(6,0,[ref('base.group_user')])]"/>
|
||||
<field name="groups_id" eval="[(6,0,[ref('base.group_user'), ref('base.group_partner_manager')])]"/>
|
||||
<field name="image">/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEP
|
||||
ERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4e
|
||||
Hh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCACEAIQDASIA
|
||||
|
||||
@@ -510,14 +510,6 @@
|
||||
<field name="currency_id" ref="CRC"/>
|
||||
<field eval="time.strftime('%Y-01-01')" name="name"/>
|
||||
</record>
|
||||
|
||||
<record id="ir_mail_server_localhost0" model="ir.mail_server">
|
||||
<field name="name">localhost</field>
|
||||
<field name="smtp_host">localhost</field>
|
||||
<field eval="25" name="smtp_port"/>
|
||||
<field eval="10" name="sequence"/>
|
||||
</record>
|
||||
|
||||
<record id="MUR" model="res.currency">
|
||||
<field name="name">MUR</field>
|
||||
<field name="symbol">Rs</field>
|
||||
|
||||
+1232
-1017
File diff suppressed because it is too large
Load Diff
+1232
-1017
File diff suppressed because it is too large
Load Diff
+1245
-1027
File diff suppressed because it is too large
Load Diff
+1286
-1050
File diff suppressed because it is too large
Load Diff
+1108
-900
File diff suppressed because it is too large
Load Diff
+1292
-1056
File diff suppressed because it is too large
Load Diff
+1243
-1025
File diff suppressed because it is too large
Load Diff
+1294
-1055
File diff suppressed because it is too large
Load Diff
+1296
-1054
File diff suppressed because it is too large
Load Diff
+1248
-1024
File diff suppressed because it is too large
Load Diff
+1803
-1338
File diff suppressed because it is too large
Load Diff
+1284
-1048
File diff suppressed because it is too large
Load Diff
+1516
-1102
File diff suppressed because it is too large
Load Diff
+3427
-1394
File diff suppressed because it is too large
Load Diff
+1301
-1047
File diff suppressed because it is too large
Load Diff
+1295
-1056
File diff suppressed because it is too large
Load Diff
+1349
-1090
File diff suppressed because it is too large
Load Diff
+3459
-2425
File diff suppressed because it is too large
Load Diff
+1389
-1091
File diff suppressed because it is too large
Load Diff
+1232
-1017
File diff suppressed because it is too large
Load Diff
+1236
-1018
File diff suppressed because it is too large
Load Diff
+1482
-1240
File diff suppressed because it is too large
Load Diff
+1232
-1017
File diff suppressed because it is too large
Load Diff
+1284
-1048
File diff suppressed because it is too large
Load Diff
+1232
-1017
File diff suppressed because it is too large
Load Diff
+1303
-1061
File diff suppressed because it is too large
Load Diff
+1345
-1202
File diff suppressed because it is too large
Load Diff
+1294
-1055
File diff suppressed because it is too large
Load Diff
+1259
-1035
File diff suppressed because it is too large
Load Diff
+1283
-1047
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1512
-1260
File diff suppressed because it is too large
Load Diff
+4783
-1534
File diff suppressed because it is too large
Load Diff
+1928
-1325
File diff suppressed because it is too large
Load Diff
+1238
-1020
File diff suppressed because it is too large
Load Diff
+1245
-1024
File diff suppressed because it is too large
Load Diff
+1935
-1390
File diff suppressed because it is too large
Load Diff
+1337
-1078
File diff suppressed because it is too large
Load Diff
+1289
-1053
File diff suppressed because it is too large
Load Diff
+1252
-1028
File diff suppressed because it is too large
Load Diff
+1269
-1033
File diff suppressed because it is too large
Load Diff
+1264
-1025
File diff suppressed because it is too large
Load Diff
+1292
-1053
File diff suppressed because it is too large
Load Diff
+1238
-1020
File diff suppressed because it is too large
Load Diff
+1761
-1176
File diff suppressed because it is too large
Load Diff
+1287
-1051
File diff suppressed because it is too large
Load Diff
+1458
-1185
File diff suppressed because it is too large
Load Diff
+1236
-1018
File diff suppressed because it is too large
Load Diff
+1576
-1291
File diff suppressed because it is too large
Load Diff
+1504
-1221
File diff suppressed because it is too large
Load Diff
+1564
-1228
File diff suppressed because it is too large
Load Diff
+3313
-1304
File diff suppressed because it is too large
Load Diff
+1548
-1137
File diff suppressed because it is too large
Load Diff
+1293
-1054
File diff suppressed because it is too large
Load Diff
+4283
-1351
File diff suppressed because it is too large
Load Diff
+1232
-1017
File diff suppressed because it is too large
Load Diff
+1290
-1051
File diff suppressed because it is too large
Load Diff
+1346
-1087
File diff suppressed because it is too large
Load Diff
+1287
-1048
File diff suppressed because it is too large
Load Diff
+1235
-1017
File diff suppressed because it is too large
Load Diff
+1232
-1017
File diff suppressed because it is too large
Load Diff
+1296
-1054
File diff suppressed because it is too large
Load Diff
+1277
-1041
File diff suppressed because it is too large
Load Diff
+1232
-1017
File diff suppressed because it is too large
Load Diff
+1261
-1034
File diff suppressed because it is too large
Load Diff
+1398
-1135
File diff suppressed because it is too large
Load Diff
+1239
-1024
File diff suppressed because it is too large
Load Diff
+1275
-1042
File diff suppressed because it is too large
Load Diff
@@ -22,17 +22,16 @@
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import tools
|
||||
|
||||
import netsvc
|
||||
from osv import fields,osv
|
||||
from report.report_sxw import report_sxw, report_rml
|
||||
from tools.config import config
|
||||
from tools.safe_eval import safe_eval as eval
|
||||
from tools.translate import _
|
||||
from socket import gethostname
|
||||
import time
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp import netsvc, tools
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.report.report_sxw import report_sxw, report_rml
|
||||
from openerp.tools.config import config
|
||||
from openerp.tools.safe_eval import safe_eval as eval
|
||||
from openerp.tools.translate import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -42,7 +41,7 @@ class actions(osv.osv):
|
||||
_order = 'name'
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=64, required=True),
|
||||
'type': fields.char('Action Type', required=True, size=32,readonly=True),
|
||||
'type': fields.char('Action Type', required=True, size=32),
|
||||
'usage': fields.char('Action Usage', size=32),
|
||||
'help': fields.text('Action description',
|
||||
help='Optional help text for the users with a description of the target view, such as its usage and purpose.',
|
||||
@@ -615,16 +614,6 @@ class actions_server(osv.osv):
|
||||
|
||||
if action.state == 'email':
|
||||
email_from = config['email_from']
|
||||
address = str(action.email)
|
||||
try:
|
||||
address = eval(str(action.email), cxt)
|
||||
except:
|
||||
pass
|
||||
|
||||
if not address:
|
||||
_logger.info('No partner email address specified, not sending any email.')
|
||||
continue
|
||||
|
||||
if not email_from:
|
||||
_logger.debug('--email-from command line option is not specified, using a fallback value instead.')
|
||||
if user.email:
|
||||
@@ -632,16 +621,27 @@ class actions_server(osv.osv):
|
||||
else:
|
||||
email_from = "%s@%s" % (user.login, gethostname())
|
||||
|
||||
try:
|
||||
address = eval(str(action.email), cxt)
|
||||
except Exception:
|
||||
address = str(action.email)
|
||||
|
||||
if not address:
|
||||
_logger.info('No partner email address specified, not sending any email.')
|
||||
continue
|
||||
|
||||
# handle single and multiple recipient addresses
|
||||
addresses = address if isinstance(address, (tuple, list)) else [address]
|
||||
subject = self.merge_message(cr, uid, action.subject, action, context)
|
||||
body = self.merge_message(cr, uid, action.message, action, context)
|
||||
|
||||
ir_mail_server = self.pool.get('ir.mail_server')
|
||||
msg = ir_mail_server.build_email(email_from, [address], subject, body)
|
||||
msg = ir_mail_server.build_email(email_from, addresses, subject, body)
|
||||
res_email = ir_mail_server.send_email(cr, uid, msg)
|
||||
if res_email:
|
||||
_logger.info('Email successfully sent to: %s', address)
|
||||
_logger.info('Email successfully sent to: %s', addresses)
|
||||
else:
|
||||
_logger.warning('Failed to send email to: %s', address)
|
||||
_logger.warning('Failed to send email to: %s', addresses)
|
||||
|
||||
if action.state == 'trigger':
|
||||
wf_service = netsvc.LocalService("workflow")
|
||||
@@ -671,7 +671,7 @@ class actions_server(osv.osv):
|
||||
context['object'] = obj
|
||||
for i in expr:
|
||||
context['active_id'] = i.id
|
||||
result = self.run(cr, uid, [action.loop_action.id], context)
|
||||
self.run(cr, uid, [action.loop_action.id], context)
|
||||
|
||||
if action.state == 'object_write':
|
||||
res = {}
|
||||
@@ -716,8 +716,6 @@ class actions_server(osv.osv):
|
||||
expr = exp.value
|
||||
res[exp.col1.name] = expr
|
||||
|
||||
obj_pool = None
|
||||
res_id = False
|
||||
obj_pool = self.pool.get(action.srcmodel_id.model)
|
||||
res_id = obj_pool.create(cr, uid, res)
|
||||
if action.record_id:
|
||||
@@ -736,7 +734,7 @@ class actions_server(osv.osv):
|
||||
model = action.copy_object.split(',')[0]
|
||||
cid = action.copy_object.split(',')[1]
|
||||
obj_pool = self.pool.get(model)
|
||||
res_id = obj_pool.copy(cr, uid, int(cid), res)
|
||||
obj_pool.copy(cr, uid, int(cid), res)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
@@ -19,13 +19,163 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import hashlib
|
||||
import itertools
|
||||
import os
|
||||
import re
|
||||
|
||||
from osv import fields,osv
|
||||
from osv.orm import except_orm
|
||||
import tools
|
||||
from openerp import tools
|
||||
from openerp.osv import fields,osv
|
||||
|
||||
class ir_attachment(osv.osv):
|
||||
"""Attachments are used to link binary files or url to any openerp document.
|
||||
|
||||
External attachment storage
|
||||
---------------------------
|
||||
|
||||
The 'data' function field (_data_get,data_set) is implemented using
|
||||
_file_read, _file_write and _file_delete which can be overridden to
|
||||
implement other storage engines, shuch methods should check for other
|
||||
location pseudo uri (example: hdfs://hadoppserver)
|
||||
|
||||
The default implementation is the file:dirname location that stores files
|
||||
on the local filesystem using name based on their sha1 hash
|
||||
"""
|
||||
def _name_get_resname(self, cr, uid, ids, object, method, context):
|
||||
data = {}
|
||||
for attachment in self.browse(cr, uid, ids, context=context):
|
||||
model_object = attachment.res_model
|
||||
res_id = attachment.res_id
|
||||
if model_object and res_id:
|
||||
model_pool = self.pool.get(model_object)
|
||||
res = model_pool.name_get(cr,uid,[res_id],context)
|
||||
res_name = res and res[0][1] or False
|
||||
if res_name:
|
||||
field = self._columns.get('res_name',False)
|
||||
if field and len(res_name) > field.size:
|
||||
res_name = res_name[:field.size-3] + '...'
|
||||
data[attachment.id] = res_name
|
||||
else:
|
||||
data[attachment.id] = False
|
||||
return data
|
||||
|
||||
# 'data' field implementation
|
||||
def _full_path(self, cr, uid, location, path):
|
||||
# location = 'file:filestore'
|
||||
assert location.startswith('file:'), "Unhandled filestore location %s" % location
|
||||
location = location[5:]
|
||||
|
||||
# sanitize location name and path
|
||||
location = re.sub('[.]','',location)
|
||||
location = location.strip('/\\')
|
||||
|
||||
path = re.sub('[.]','',path)
|
||||
path = path.strip('/\\')
|
||||
return os.path.join(tools.config['root_path'], location, cr.dbname, path)
|
||||
|
||||
def _file_read(self, cr, uid, location, fname, bin_size=False):
|
||||
full_path = self._full_path(cr, uid, location, fname)
|
||||
r = ''
|
||||
try:
|
||||
if bin_size:
|
||||
r = os.path.getsize(full_path)
|
||||
else:
|
||||
r = open(full_path).read().encode('base64')
|
||||
except IOError:
|
||||
_logger.error("_read_file reading %s",full_path)
|
||||
return r
|
||||
|
||||
def _file_write(self, cr, uid, location, value):
|
||||
bin_value = value.decode('base64')
|
||||
fname = hashlib.sha1(bin_value).hexdigest()
|
||||
# scatter files across 1024 dirs
|
||||
# we use '/' in the db (even on windows)
|
||||
fname = fname[:3] + '/' + fname
|
||||
full_path = self._full_path(cr, uid, location, fname)
|
||||
try:
|
||||
dirname = os.path.dirname(full_path)
|
||||
if not os.path.isdir(dirname):
|
||||
os.makedirs(dirname)
|
||||
open(full_path,'wb').write(bin_value)
|
||||
except IOError:
|
||||
_logger.error("_file_write writing %s",full_path)
|
||||
return fname
|
||||
|
||||
def _file_delete(self, cr, uid, location, fname):
|
||||
count = self.search(cr, 1, [('store_fname','=',fname)], count=True)
|
||||
if count <= 1:
|
||||
full_path = self._full_path(cr, uid, location, fname)
|
||||
try:
|
||||
os.unlink(full_path)
|
||||
except IOError:
|
||||
# Harmless and needed for race conditions
|
||||
_logger.error("_file_delete could not unlink %s",full_path)
|
||||
|
||||
def _data_get(self, cr, uid, ids, name, arg, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
result = {}
|
||||
location = self.pool.get('ir.config_parameter').get_param(cr, uid, 'ir_attachment.location')
|
||||
bin_size = context.get('bin_size')
|
||||
for attach in self.browse(cr, uid, ids, context=context):
|
||||
if location and attach.store_fname:
|
||||
result[attach.id] = self._file_read(cr, uid, location, attach.store_fname, bin_size)
|
||||
else:
|
||||
result[attach.id] = attach.db_datas
|
||||
return result
|
||||
|
||||
def _data_set(self, cr, uid, id, name, value, arg, context=None):
|
||||
# We dont handle setting data to null
|
||||
if not value:
|
||||
return True
|
||||
if context is None:
|
||||
context = {}
|
||||
location = self.pool.get('ir.config_parameter').get_param(cr, uid, 'ir_attachment.location')
|
||||
file_size = len(value.decode('base64'))
|
||||
if location:
|
||||
attach = self.browse(cr, uid, id, context=context)
|
||||
if attach.store_fname:
|
||||
self._file_delete(cr, uid, location, attach.store_fname)
|
||||
fname = self._file_write(cr, uid, location, value)
|
||||
super(ir_attachment, self).write(cr, uid, [id], {'store_fname': fname, 'file_size': file_size}, context=context)
|
||||
else:
|
||||
super(ir_attachment, self).write(cr, uid, [id], {'db_datas': value, 'file_size': file_size}, context=context)
|
||||
return True
|
||||
|
||||
_name = 'ir.attachment'
|
||||
_columns = {
|
||||
'name': fields.char('Attachment Name',size=256, required=True),
|
||||
'datas_fname': fields.char('File Name',size=256),
|
||||
'description': fields.text('Description'),
|
||||
'res_name': fields.function(_name_get_resname, type='char', size=128, string='Resource Name', store=True),
|
||||
'res_model': fields.char('Resource Model',size=64, readonly=True, help="The database object this attachment will be attached to"),
|
||||
'res_id': fields.integer('Resource ID', readonly=True, help="The record id this is attached to"),
|
||||
'create_date': fields.datetime('Date Created', readonly=True),
|
||||
'create_uid': fields.many2one('res.users', 'Owner', readonly=True),
|
||||
'company_id': fields.many2one('res.company', 'Company', change_default=True),
|
||||
'type': fields.selection( [ ('url','URL'), ('binary','Binary'), ],
|
||||
'Type', help="Binary File or URL", required=True, change_default=True),
|
||||
'url': fields.char('Url', size=1024),
|
||||
# al: We keep shitty field names for backward compatibility with document
|
||||
'datas': fields.function(_data_get, fnct_inv=_data_set, string='File Content', type="binary", nodrop=True),
|
||||
'store_fname': fields.char('Stored Filename', size=256),
|
||||
'db_datas': fields.binary('Database Data'),
|
||||
'file_size': fields.integer('File Size'),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'type': 'binary',
|
||||
'file_size': 0,
|
||||
'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'ir.attachment', context=c),
|
||||
}
|
||||
|
||||
def _auto_init(self, cr, context=None):
|
||||
super(ir_attachment, self)._auto_init(cr, context)
|
||||
cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('ir_attachment_res_idx',))
|
||||
if not cr.fetchone():
|
||||
cr.execute('CREATE INDEX ir_attachment_res_idx ON ir_attachment (res_model, res_id)')
|
||||
cr.commit()
|
||||
|
||||
def check(self, cr, uid, ids, mode, context=None, values=None):
|
||||
"""Restricts the access to an ir.attachment, according to referred model
|
||||
In the 'document' module, it is overriden to relax this hard rule, since
|
||||
@@ -111,6 +261,8 @@ class ir_attachment(osv.osv):
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
self.check(cr, uid, ids, 'write', context=context, values=vals)
|
||||
if 'file_size' in vals:
|
||||
del vals['file_size']
|
||||
return super(ir_attachment, self).write(cr, uid, ids, vals, context)
|
||||
|
||||
def copy(self, cr, uid, id, default=None, context=None):
|
||||
@@ -119,70 +271,22 @@ class ir_attachment(osv.osv):
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
self.check(cr, uid, ids, 'unlink', context=context)
|
||||
location = self.pool.get('ir.config_parameter').get_param(cr, uid, 'ir_attachment.location')
|
||||
if location:
|
||||
for attach in self.browse(cr, uid, ids, context=context):
|
||||
if attach.store_fname:
|
||||
self._file_delete(cr, uid, location, attach.store_fname)
|
||||
return super(ir_attachment, self).unlink(cr, uid, ids, context)
|
||||
|
||||
def create(self, cr, uid, values, context=None):
|
||||
self.check(cr, uid, [], mode='create', context=context, values=values)
|
||||
if 'file_size' in values:
|
||||
del values['file_size']
|
||||
return super(ir_attachment, self).create(cr, uid, values, context)
|
||||
|
||||
def action_get(self, cr, uid, context=None):
|
||||
return self.pool.get('ir.actions.act_window').for_xml_id(
|
||||
cr, uid, 'base', 'action_attachment', context=context)
|
||||
|
||||
def _name_get_resname(self, cr, uid, ids, object, method, context):
|
||||
data = {}
|
||||
for attachment in self.browse(cr, uid, ids, context=context):
|
||||
model_object = attachment.res_model
|
||||
res_id = attachment.res_id
|
||||
if model_object and res_id:
|
||||
model_pool = self.pool.get(model_object)
|
||||
res = model_pool.name_get(cr,uid,[res_id],context)
|
||||
res_name = res and res[0][1] or False
|
||||
if res_name:
|
||||
field = self._columns.get('res_name',False)
|
||||
if field and len(res_name) > field.size:
|
||||
res_name = res_name[:field.size-3] + '...'
|
||||
data[attachment.id] = res_name
|
||||
else:
|
||||
data[attachment.id] = False
|
||||
return data
|
||||
|
||||
_name = 'ir.attachment'
|
||||
_columns = {
|
||||
'name': fields.char('Attachment Name',size=256, required=True),
|
||||
'datas': fields.binary('Data'),
|
||||
'datas_fname': fields.char('File Name',size=256),
|
||||
'description': fields.text('Description'),
|
||||
'res_name': fields.function(_name_get_resname, type='char', size=128,
|
||||
string='Resource Name', store=True),
|
||||
'res_model': fields.char('Resource Object',size=64, readonly=True,
|
||||
help="The database object this attachment will be attached to"),
|
||||
'res_id': fields.integer('Resource ID', readonly=True,
|
||||
help="The record id this is attached to"),
|
||||
'url': fields.char('Url', size=512, oldname="link"),
|
||||
'type': fields.selection(
|
||||
[ ('url','URL'), ('binary','Binary'), ],
|
||||
'Type', help="Binary File or external URL", required=True, change_default=True),
|
||||
|
||||
'create_date': fields.datetime('Date Created', readonly=True),
|
||||
'create_uid': fields.many2one('res.users', 'Owner', readonly=True),
|
||||
'company_id': fields.many2one('res.company', 'Company', change_default=True),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'type': 'binary',
|
||||
'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'ir.attachment', context=c),
|
||||
}
|
||||
|
||||
def _auto_init(self, cr, context=None):
|
||||
super(ir_attachment, self)._auto_init(cr, context)
|
||||
cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('ir_attachment_res_idx',))
|
||||
if not cr.fetchone():
|
||||
cr.execute('CREATE INDEX ir_attachment_res_idx ON ir_attachment (res_model, res_id)')
|
||||
cr.commit()
|
||||
|
||||
ir_attachment()
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
|
||||
@@ -22,11 +22,12 @@
|
||||
Store database-specific configuration parameters
|
||||
"""
|
||||
|
||||
from osv import osv,fields
|
||||
import uuid
|
||||
import datetime
|
||||
from tools import misc, config
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import osv, fields
|
||||
from openerp.tools import misc, config
|
||||
|
||||
"""
|
||||
A dictionary holding some configuration parameters to be initialized when the database is created.
|
||||
|
||||
@@ -18,24 +18,18 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import calendar
|
||||
import time
|
||||
import logging
|
||||
import threading
|
||||
import psycopg2
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
import netsvc
|
||||
import openerp
|
||||
import pooler
|
||||
import tools
|
||||
from openerp.cron import WAKE_UP_NOW
|
||||
from osv import fields, osv
|
||||
from tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from tools.safe_eval import safe_eval as eval
|
||||
from tools.translate import _
|
||||
from openerp import netsvc
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from openerp.tools.safe_eval import safe_eval as eval
|
||||
from openerp.tools.translate import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -142,130 +136,6 @@ class ir_cron(osv.osv):
|
||||
except Exception, e:
|
||||
self._handle_callback_exception(cr, uid, model_name, method_name, args, job_id, e)
|
||||
|
||||
def _run_job(self, cr, job, now):
|
||||
""" Run a given job taking care of the repetition.
|
||||
|
||||
The cursor has a lock on the job (aquired by _run_jobs_multithread()) and this
|
||||
method is run in a worker thread (spawned by _run_jobs_multithread())).
|
||||
|
||||
:param job: job to be run (as a dictionary).
|
||||
:param now: timestamp (result of datetime.now(), no need to call it multiple time).
|
||||
|
||||
"""
|
||||
try:
|
||||
nextcall = datetime.strptime(job['nextcall'], DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
numbercall = job['numbercall']
|
||||
|
||||
ok = False
|
||||
while nextcall < now and numbercall:
|
||||
if numbercall > 0:
|
||||
numbercall -= 1
|
||||
if not ok or job['doall']:
|
||||
self._callback(cr, job['user_id'], job['model'], job['function'], job['args'], job['id'])
|
||||
if numbercall:
|
||||
nextcall += _intervalTypes[job['interval_type']](job['interval_number'])
|
||||
ok = True
|
||||
addsql = ''
|
||||
if not numbercall:
|
||||
addsql = ', active=False'
|
||||
cr.execute("UPDATE ir_cron SET nextcall=%s, numbercall=%s"+addsql+" WHERE id=%s",
|
||||
(nextcall.strftime(DEFAULT_SERVER_DATETIME_FORMAT), numbercall, job['id']))
|
||||
|
||||
if numbercall:
|
||||
# Reschedule our own main cron thread if necessary.
|
||||
# This is really needed if this job runs longer than its rescheduling period.
|
||||
nextcall = calendar.timegm(nextcall.timetuple())
|
||||
openerp.cron.schedule_wakeup(nextcall, cr.dbname)
|
||||
finally:
|
||||
cr.commit()
|
||||
cr.close()
|
||||
openerp.cron.release_thread_slot()
|
||||
|
||||
def _run_jobs_multithread(self):
|
||||
# TODO remove 'check' argument from addons/base_action_rule/base_action_rule.py
|
||||
""" Process the cron jobs by spawning worker threads.
|
||||
|
||||
This selects in database all the jobs that should be processed. It then
|
||||
tries to lock each of them and, if it succeeds, spawns a thread to run
|
||||
the cron job (if it doesn't succeed, it means the job was already
|
||||
locked to be taken care of by another thread).
|
||||
|
||||
The cursor used to lock the job in database is given to the worker
|
||||
thread (which has to close it itself).
|
||||
|
||||
"""
|
||||
db = self.pool.db
|
||||
cr = db.cursor()
|
||||
db_name = db.dbname
|
||||
try:
|
||||
jobs = {} # mapping job ids to jobs for all jobs being processed.
|
||||
now = datetime.now()
|
||||
# Careful to compare timestamps with 'UTC' - everything is UTC as of v6.1.
|
||||
cr.execute("""SELECT * FROM ir_cron
|
||||
WHERE numbercall != 0
|
||||
AND active AND nextcall <= (now() at time zone 'UTC')
|
||||
ORDER BY priority""")
|
||||
for job in cr.dictfetchall():
|
||||
if not openerp.cron.get_thread_slots():
|
||||
break
|
||||
jobs[job['id']] = job
|
||||
|
||||
task_cr = db.cursor()
|
||||
try:
|
||||
# Try to grab an exclusive lock on the job row from within the task transaction
|
||||
acquired_lock = False
|
||||
task_cr.execute("""SELECT *
|
||||
FROM ir_cron
|
||||
WHERE id=%s
|
||||
FOR UPDATE NOWAIT""",
|
||||
(job['id'],), log_exceptions=False)
|
||||
acquired_lock = True
|
||||
except psycopg2.OperationalError, e:
|
||||
if e.pgcode == '55P03':
|
||||
# Class 55: Object not in prerequisite state; 55P03: lock_not_available
|
||||
_logger.debug('Another process/thread is already busy executing job `%s`, skipping it.', job['name'])
|
||||
continue
|
||||
else:
|
||||
# Unexpected OperationalError
|
||||
raise
|
||||
finally:
|
||||
if not acquired_lock:
|
||||
# we're exiting due to an exception while acquiring the lot
|
||||
task_cr.close()
|
||||
|
||||
# Got the lock on the job row, now spawn a thread to execute it in the transaction with the lock
|
||||
task_thread = threading.Thread(target=self._run_job, name=job['name'], args=(task_cr, job, now))
|
||||
# force non-daemon task threads (the runner thread must be daemon, and this property is inherited by default)
|
||||
task_thread.setDaemon(False)
|
||||
openerp.cron.take_thread_slot()
|
||||
task_thread.start()
|
||||
_logger.debug('Cron execution thread for job `%s` spawned', job['name'])
|
||||
|
||||
# Find next earliest job ignoring currently processed jobs (by this and other cron threads)
|
||||
find_next_time_query = """SELECT min(nextcall) AS min_next_call
|
||||
FROM ir_cron WHERE numbercall != 0 AND active"""
|
||||
if jobs:
|
||||
cr.execute(find_next_time_query + " AND id NOT IN %s", (tuple(jobs.keys()),))
|
||||
else:
|
||||
cr.execute(find_next_time_query)
|
||||
next_call = cr.dictfetchone()['min_next_call']
|
||||
|
||||
if next_call:
|
||||
next_call = calendar.timegm(time.strptime(next_call, DEFAULT_SERVER_DATETIME_FORMAT))
|
||||
else:
|
||||
# no matching cron job found in database, re-schedule arbitrarily in 1 day,
|
||||
# this delay will likely be modified when running jobs complete their tasks
|
||||
next_call = time.time() + (24*3600)
|
||||
|
||||
openerp.cron.schedule_wakeup(next_call, db_name)
|
||||
|
||||
except Exception, ex:
|
||||
_logger.warning('Exception in cron:', exc_info=True)
|
||||
|
||||
finally:
|
||||
cr.commit()
|
||||
cr.close()
|
||||
|
||||
def _process_job(self, cr, job):
|
||||
""" Run a given job taking care of the repetition.
|
||||
|
||||
@@ -356,7 +226,7 @@ class ir_cron(osv.osv):
|
||||
_logger.warning('Tried to poll an undefined table on database %s.', db_name)
|
||||
else:
|
||||
raise
|
||||
except Exception, ex:
|
||||
except Exception:
|
||||
_logger.warning('Exception in cron:', exc_info=True)
|
||||
|
||||
finally:
|
||||
@@ -365,19 +235,6 @@ class ir_cron(osv.osv):
|
||||
|
||||
return False
|
||||
|
||||
def update_running_cron(self, cr):
|
||||
""" Schedule as soon as possible a wake-up for this database. """
|
||||
# Verify whether the server is already started and thus whether we need to commit
|
||||
# immediately our changes and restart the cron agent in order to apply the change
|
||||
# immediately. The commit() is needed because as soon as the cron is (re)started it
|
||||
# will query the database with its own cursor, possibly before the end of the
|
||||
# current transaction.
|
||||
# This commit() is not an issue in most cases, but we must absolutely avoid it
|
||||
# when the server is only starting or loading modules (hence the test on pool._init).
|
||||
if not self.pool._init:
|
||||
cr.commit()
|
||||
openerp.cron.schedule_wakeup(WAKE_UP_NOW, self.pool.db.dbname)
|
||||
|
||||
def _try_lock(self, cr, uid, ids, context=None):
|
||||
"""Try to grab a dummy exclusive write-lock to the rows with the given ids,
|
||||
to make sure a following write() or unlink() will not block due
|
||||
@@ -393,20 +250,16 @@ class ir_cron(osv.osv):
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
res = super(ir_cron, self).create(cr, uid, vals, context=context)
|
||||
self.update_running_cron(cr)
|
||||
return res
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
self._try_lock(cr, uid, ids, context)
|
||||
res = super(ir_cron, self).write(cr, uid, ids, vals, context=context)
|
||||
self.update_running_cron(cr)
|
||||
return res
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
self._try_lock(cr, uid, ids, context)
|
||||
res = super(ir_cron, self).unlink(cr, uid, ids, context=context)
|
||||
self.update_running_cron(cr)
|
||||
return res
|
||||
ir_cron()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from osv import fields,osv
|
||||
from openerp.osv import fields, osv
|
||||
|
||||
class ir_default(osv.osv):
|
||||
_name = 'ir.default'
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from osv import fields,osv
|
||||
from openerp.osv import fields,osv
|
||||
|
||||
|
||||
class ir_exports(osv.osv):
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import collections
|
||||
import datetime
|
||||
import functools
|
||||
import operator
|
||||
|
||||
@@ -20,13 +20,10 @@
|
||||
##############################################################################
|
||||
|
||||
from openerp import exceptions
|
||||
from osv import osv, fields
|
||||
from tools.translate import _
|
||||
from openerp.osv import osv, fields
|
||||
from openerp.tools.translate import _
|
||||
|
||||
class ir_filters(osv.osv):
|
||||
'''
|
||||
Filters
|
||||
'''
|
||||
_name = 'ir.filters'
|
||||
_description = 'Filters'
|
||||
|
||||
|
||||
@@ -31,8 +31,7 @@ import re
|
||||
import smtplib
|
||||
import threading
|
||||
|
||||
from osv import osv
|
||||
from osv import fields
|
||||
from openerp.osv import osv, fields
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools import html2text
|
||||
import openerp.tools as tools
|
||||
@@ -183,10 +182,12 @@ class ir_mail_server(osv.osv):
|
||||
"(this is very verbose and may include confidential info!)"),
|
||||
'sequence': fields.integer('Priority', help="When no specific mail server is requested for a mail, the highest priority one "
|
||||
"is used. Default priority is 10 (smaller number = higher priority)"),
|
||||
'active': fields.boolean('Active')
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'smtp_port': 25,
|
||||
'active': True,
|
||||
'sequence': 10,
|
||||
'smtp_encryption': 'none',
|
||||
}
|
||||
@@ -228,7 +229,7 @@ class ir_mail_server(osv.osv):
|
||||
:param int port: SMTP port to connect to
|
||||
:param user: optional username to authenticate with
|
||||
:param password: optional password to authenticate with
|
||||
:param string encryption: optional: ``'ssl'`` | ``'starttls'``
|
||||
:param string encryption: optional, ``'ssl'`` | ``'starttls'``
|
||||
:param bool smtp_debug: toggle debugging of SMTP sessions (all i/o
|
||||
will be output in logs)
|
||||
"""
|
||||
|
||||
@@ -158,9 +158,10 @@ class ir_model(osv.osv):
|
||||
if context is None: context = {}
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
if not context.get(MODULE_UNINSTALL_FLAG) and \
|
||||
any(model.state != 'manual' for model in self.browse(cr, user, ids, context)):
|
||||
raise except_orm(_('Error'), _("Model '%s' contains module data and cannot be removed!") % (model.name,))
|
||||
if not context.get(MODULE_UNINSTALL_FLAG):
|
||||
for model in self.browse(cr, user, ids, context):
|
||||
if model.state != 'manual':
|
||||
raise except_orm(_('Error'), _("Model '%s' contains module data and cannot be removed!") % (model.name,))
|
||||
|
||||
self._drop_table(cr, user, ids, context)
|
||||
res = super(ir_model, self).unlink(cr, user, ids, context)
|
||||
@@ -256,7 +257,7 @@ class ir_model_fields(osv.osv):
|
||||
'selection': "",
|
||||
'domain': "[]",
|
||||
'name': 'x_',
|
||||
'state': lambda self,cr,uid,ctx={}: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
|
||||
'state': lambda self,cr,uid,ctx=None: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
|
||||
'on_delete': 'set null',
|
||||
'select_level': '0',
|
||||
'size': 64,
|
||||
@@ -271,7 +272,7 @@ class ir_model_fields(osv.osv):
|
||||
except Exception:
|
||||
_logger.warning('Invalid selection list definition for fields.selection', exc_info=True)
|
||||
raise except_orm(_('Error'),
|
||||
_("The Selection Options expression is not a valid Pythonic expression." \
|
||||
_("The Selection Options expression is not a valid Pythonic expression."
|
||||
"Please provide an expression in the [('key','Label'), ...] format."))
|
||||
|
||||
check = True
|
||||
@@ -514,7 +515,7 @@ class ir_model_constraint(Model):
|
||||
# double-check we are really going to delete all the owners of this schema element
|
||||
cr.execute("""SELECT id from ir_model_constraint where name=%s""", (data.name,))
|
||||
external_ids = [x[0] for x in cr.fetchall()]
|
||||
if (set(external_ids)-ids_set):
|
||||
if set(external_ids)-ids_set:
|
||||
# as installed modules have defined this element we must not delete it!
|
||||
continue
|
||||
|
||||
@@ -567,13 +568,12 @@ class ir_model_relation(Model):
|
||||
ids.reverse()
|
||||
for data in self.browse(cr, uid, ids, context):
|
||||
model = data.model
|
||||
model_obj = self.pool.get(model)
|
||||
name = openerp.tools.ustr(data.name)
|
||||
|
||||
# double-check we are really going to delete all the owners of this schema element
|
||||
cr.execute("""SELECT id from ir_model_relation where name = %s""", (data.name,))
|
||||
external_ids = [x[0] for x in cr.fetchall()]
|
||||
if (set(external_ids)-ids_set):
|
||||
if set(external_ids)-ids_set:
|
||||
# as installed modules have defined this element we must not delete it!
|
||||
continue
|
||||
|
||||
@@ -585,7 +585,7 @@ class ir_model_relation(Model):
|
||||
|
||||
# drop m2m relation tables
|
||||
for table in to_drop_table:
|
||||
cr.execute('DROP TABLE %s CASCADE'% (table),)
|
||||
cr.execute('DROP TABLE %s CASCADE'% table,)
|
||||
_logger.info('Dropped table %s', table)
|
||||
|
||||
cr.commit()
|
||||
@@ -862,7 +862,7 @@ class ir_model_data(osv.osv):
|
||||
res = self.read(cr, uid, data_id, ['model', 'res_id'])
|
||||
if not res['res_id']:
|
||||
raise ValueError('No such external ID currently defined in the system: %s.%s' % (module, xml_id))
|
||||
return (res['model'], res['res_id'])
|
||||
return res['model'], res['res_id']
|
||||
|
||||
def get_object(self, cr, uid, module, xml_id, context=None):
|
||||
"""Returns a browsable record for the given module name and xml_id or raise ValueError if not found"""
|
||||
@@ -903,7 +903,7 @@ class ir_model_data(osv.osv):
|
||||
# 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)
|
||||
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('.')
|
||||
if (not xml_id) and (not self.doinit):
|
||||
return False
|
||||
@@ -1073,7 +1073,6 @@ class ir_model_data(osv.osv):
|
||||
if model == 'ir.model.fields')
|
||||
|
||||
ir_model_relation = self.pool.get('ir.model.relation')
|
||||
relation_ids = ir_model_relation.search(cr, uid, [('module', 'in', modules_to_remove)])
|
||||
ir_module_module = self.pool.get('ir.module.module')
|
||||
modules_to_remove_ids = ir_module_module.search(cr, uid, [('name', 'in', modules_to_remove)])
|
||||
relation_ids = ir_model_relation.search(cr, uid, [('module', 'in', modules_to_remove_ids)])
|
||||
|
||||
@@ -246,8 +246,8 @@
|
||||
<tree string="External Identifiers">
|
||||
<field name="complete_name"/>
|
||||
<field name="display_name"/>
|
||||
<field name="res_id"/>
|
||||
<field name="model" groups="base.group_no_one"/>
|
||||
<field name="res_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -255,12 +255,10 @@
|
||||
<field name="model">ir.model.data</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="External Identifiers">
|
||||
<field name="name"
|
||||
filter_domain="['|', '|', ('name','ilike',self), ('model','ilike',self), ('module','ilike',self)]"
|
||||
string="External Identifier"/>
|
||||
<filter icon="terp-camera_test"
|
||||
string="Updatable"
|
||||
domain="[('noupdate', '=', False)]"/>
|
||||
<field name="name" filter_domain="[('name','ilike',self)]" string="External Identifier"/>
|
||||
<filter string="Updatable" domain="[('noupdate', '=', False)]"/>
|
||||
<field name="module"/>
|
||||
<field name="model"/>
|
||||
<field name="res_id"/>
|
||||
<field name="noupdate"/>
|
||||
<group expand="0" string="Group By...">
|
||||
|
||||
@@ -19,10 +19,11 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from osv import osv
|
||||
from openerp.osv import osv
|
||||
|
||||
|
||||
class ir_needaction_mixin(osv.AbstractModel):
|
||||
'''Mixin class for objects using the need action feature.
|
||||
"""Mixin class for objects using the need action feature.
|
||||
|
||||
Need action feature can be used by models that have to be able to
|
||||
signal that an action is required on a particular record. If in
|
||||
@@ -36,7 +37,7 @@ class ir_needaction_mixin(osv.AbstractModel):
|
||||
|
||||
This class also offers several global services:
|
||||
- ``_needaction_count``: returns the number of actions uid has to perform
|
||||
'''
|
||||
"""
|
||||
|
||||
_name = 'ir.needaction_mixin'
|
||||
_needaction = True
|
||||
@@ -55,9 +56,10 @@ class ir_needaction_mixin(osv.AbstractModel):
|
||||
# "Need action" API
|
||||
#------------------------------------------------------
|
||||
|
||||
def _needaction_count(self, cr, uid, domain=[], context=None):
|
||||
def _needaction_count(self, cr, uid, domain=None, context=None):
|
||||
""" Get the number of actions uid has to perform. """
|
||||
dom = self._needaction_domain_get(cr, uid, context=context)
|
||||
if not dom:
|
||||
return 0
|
||||
return self.search(cr, uid, (domain or []) +dom, context=context, count=True)
|
||||
res = self.search(cr, uid, (domain or []) + dom, limit=100, order='id DESC', context=context)
|
||||
return len(res)
|
||||
|
||||
@@ -18,15 +18,13 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from osv import fields, osv, expression
|
||||
import time
|
||||
from operator import itemgetter
|
||||
from functools import partial
|
||||
import tools
|
||||
from tools.safe_eval import safe_eval as eval
|
||||
from tools.misc import unquote as unquote
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp import tools
|
||||
from openerp.osv import fields, osv, expression
|
||||
from openerp.tools.safe_eval import safe_eval as eval
|
||||
from openerp.tools.misc import unquote as unquote
|
||||
|
||||
class ir_rule(osv.osv):
|
||||
_name = 'ir.rule'
|
||||
@@ -52,7 +50,7 @@ class ir_rule(osv.osv):
|
||||
eval_context = self._eval_context(cr, uid)
|
||||
for rule in self.browse(cr, uid, ids, context):
|
||||
if rule.domain_force:
|
||||
res[rule.id] = expression.normalize(eval(rule.domain_force, eval_context))
|
||||
res[rule.id] = expression.normalize_domain(eval(rule.domain_force, eval_context))
|
||||
else:
|
||||
res[rule.id] = []
|
||||
return res
|
||||
@@ -130,7 +128,7 @@ class ir_rule(osv.osv):
|
||||
for rule in self.browse(cr, SUPERUSER_ID, rule_ids):
|
||||
# read 'domain' as UID to have the correct eval context for the rule.
|
||||
rule_domain = self.read(cr, uid, rule.id, ['domain'])['domain']
|
||||
dom = expression.normalize(rule_domain)
|
||||
dom = expression.normalize_domain(rule_domain)
|
||||
for group in rule.groups:
|
||||
if group in user.groups_id:
|
||||
group_domains.setdefault(group, []).append(dom)
|
||||
|
||||
@@ -22,10 +22,9 @@
|
||||
import logging
|
||||
import time
|
||||
|
||||
from osv import osv, fields
|
||||
from tools.translate import _
|
||||
|
||||
import openerp
|
||||
from openerp.osv import osv
|
||||
from openerp.tools.translate import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -140,7 +139,7 @@ class ir_sequence(openerp.osv.osv.osv):
|
||||
values = self._add_missing_default_values(cr, uid, values, context)
|
||||
values['id'] = super(ir_sequence, self).create(cr, uid, values, context)
|
||||
if values['implementation'] == 'standard':
|
||||
f = self._create_sequence(cr, values['id'], values['number_increment'], values['number_next'])
|
||||
self._create_sequence(cr, values['id'], values['number_increment'], values['number_next'])
|
||||
return values['id']
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
|
||||
@@ -19,12 +19,12 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import tools
|
||||
import logging
|
||||
|
||||
from openerp import tools
|
||||
import openerp.modules
|
||||
from openerp.osv import fields, osv
|
||||
from tools.translate import _
|
||||
from openerp.tools.translate import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -134,7 +134,7 @@ class ir_translation_import_cursor(object):
|
||||
""" % (self._parent_table, self._table_name, self._parent_table, find_expr))
|
||||
|
||||
if self._debug:
|
||||
cr.execute('SELECT COUNT(*) FROM ONLY %s' % (self._parent_table))
|
||||
cr.execute('SELECT COUNT(*) FROM ONLY %s' % self._parent_table)
|
||||
c1 = cr.fetchone()[0]
|
||||
cr.execute('SELECT COUNT(*) FROM ONLY %s AS irt, %s AS ti WHERE %s' % \
|
||||
(self._parent_table, self._table_name, find_expr))
|
||||
@@ -217,11 +217,11 @@ class ir_translation(osv.osv):
|
||||
def _get_ids(self, cr, uid, name, tt, lang, ids):
|
||||
translations = dict.fromkeys(ids, False)
|
||||
if ids:
|
||||
cr.execute('select res_id,value ' \
|
||||
'from ir_translation ' \
|
||||
'where lang=%s ' \
|
||||
'and type=%s ' \
|
||||
'and name=%s ' \
|
||||
cr.execute('select res_id,value '
|
||||
'from ir_translation '
|
||||
'where lang=%s '
|
||||
'and type=%s '
|
||||
'and name=%s '
|
||||
'and res_id IN %s',
|
||||
(lang,tt,name,tuple(ids)))
|
||||
for res_id, value in cr.fetchall():
|
||||
@@ -237,10 +237,10 @@ class ir_translation(osv.osv):
|
||||
self._get_ids.clear_cache(self, uid, name, tt, lang, res_id)
|
||||
self._get_source.clear_cache(self, uid, name, tt, lang)
|
||||
|
||||
cr.execute('delete from ir_translation ' \
|
||||
'where lang=%s ' \
|
||||
'and type=%s ' \
|
||||
'and name=%s ' \
|
||||
cr.execute('delete from ir_translation '
|
||||
'where lang=%s '
|
||||
'and type=%s '
|
||||
'and name=%s '
|
||||
'and res_id IN %s',
|
||||
(lang,tt,name,tuple(ids),))
|
||||
for id in ids:
|
||||
|
||||
@@ -23,11 +23,11 @@
|
||||
import base64
|
||||
import re
|
||||
import threading
|
||||
from tools.safe_eval import safe_eval as eval
|
||||
import tools
|
||||
from openerp.tools.safe_eval import safe_eval as eval
|
||||
from openerp import tools
|
||||
import openerp.modules
|
||||
from osv import fields, osv
|
||||
from tools.translate import _
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
from openerp import SUPERUSER_ID
|
||||
|
||||
def one_in(setA, setB):
|
||||
@@ -43,14 +43,17 @@ class ir_ui_menu(osv.osv):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.cache_lock = threading.RLock()
|
||||
self.clear_cache()
|
||||
r = super(ir_ui_menu, self).__init__(*args, **kwargs)
|
||||
self._cache = {}
|
||||
super(ir_ui_menu, self).__init__(*args, **kwargs)
|
||||
self.pool.get('ir.model.access').register_cache_clearing_method(self._name, 'clear_cache')
|
||||
return r
|
||||
|
||||
def clear_cache(self):
|
||||
with self.cache_lock:
|
||||
# radical but this doesn't frequently happen
|
||||
if self._cache:
|
||||
# Normally this is done by openerp.tools.ormcache
|
||||
# but since we do not use it, set it by ourself.
|
||||
self.pool._any_cache_cleared = True
|
||||
self._cache = {}
|
||||
|
||||
def _filter_visible_menus(self, cr, uid, ids, context=None):
|
||||
@@ -62,7 +65,7 @@ class ir_ui_menu(osv.osv):
|
||||
modelaccess = self.pool.get('ir.model.access')
|
||||
user_groups = set(self.pool.get('res.users').read(cr, SUPERUSER_ID, uid, ['groups_id'])['groups_id'])
|
||||
result = []
|
||||
for menu in self.browse(cr, SUPERUSER_ID, ids, context=context):
|
||||
for menu in self.browse(cr, uid, ids, context=context):
|
||||
# this key works because user access rights are all based on user's groups (cfr ir_model_access.check)
|
||||
key = (cr.dbname, menu.id, tuple(user_groups))
|
||||
if key in self._cache:
|
||||
@@ -140,7 +143,7 @@ class ir_ui_menu(osv.osv):
|
||||
return res
|
||||
|
||||
def _get_full_name(self, cr, uid, ids, name=None, args=None, context=None):
|
||||
if context == None:
|
||||
if context is None:
|
||||
context = {}
|
||||
res = {}
|
||||
for elmt in self.browse(cr, uid, ids, context=context):
|
||||
@@ -164,9 +167,22 @@ class ir_ui_menu(osv.osv):
|
||||
self.clear_cache()
|
||||
return super(ir_ui_menu, self).write(*args, **kwargs)
|
||||
|
||||
def unlink(self, *args, **kwargs):
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
# Detach children and promote them to top-level, because it would be unwise to
|
||||
# cascade-delete submenus blindly. We also can't use ondelete=set null because
|
||||
# that is not supported when _parent_store is used (would silently corrupt it).
|
||||
# TODO: ideally we should move them under a generic "Orphans" menu somewhere?
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
local_context = dict(context or {})
|
||||
local_context['ir.ui.menu.full_list'] = True
|
||||
direct_children_ids = self.search(cr, uid, [('parent_id', 'in', ids)], context=local_context)
|
||||
if direct_children_ids:
|
||||
self.write(cr, uid, direct_children_ids, {'parent_id': False})
|
||||
|
||||
result = super(ir_ui_menu, self).unlink(cr, uid, ids, context=context)
|
||||
self.clear_cache()
|
||||
return super(ir_ui_menu, self).unlink(*args, **kwargs)
|
||||
return result
|
||||
|
||||
def copy(self, cr, uid, id, default=None, context=None):
|
||||
ir_values_obj = self.pool.get('ir.values')
|
||||
@@ -178,7 +194,7 @@ class ir_ui_menu(osv.osv):
|
||||
next_num=int(concat[0])+1
|
||||
datas['name']=rex.sub(('(%d)'%next_num),datas['name'])
|
||||
else:
|
||||
datas['name']=datas['name']+'(1)'
|
||||
datas['name'] += '(1)'
|
||||
self.write(cr,uid,[res],{'name':datas['name']})
|
||||
ids = ir_values_obj.search(cr, uid, [
|
||||
('model', '=', 'ir.ui.menu'),
|
||||
@@ -265,17 +281,33 @@ class ir_ui_menu(osv.osv):
|
||||
|
||||
return res
|
||||
|
||||
def _get_needaction(self, cr, uid, ids, field_names, args, context=None):
|
||||
def _get_needaction_enabled(self, cr, uid, ids, field_names, args, context=None):
|
||||
""" needaction_enabled: tell whether the menu has a related action
|
||||
that uses the needaction mechanism. """
|
||||
res = dict.fromkeys(ids, False)
|
||||
for menu in self.browse(cr, uid, ids, context=context):
|
||||
if menu.action and menu.action.type in ('ir.actions.act_window', 'ir.actions.client') and menu.action.res_model:
|
||||
obj = self.pool.get(menu.action.res_model)
|
||||
if obj and obj._needaction:
|
||||
res[menu.id] = True
|
||||
return res
|
||||
|
||||
def get_needaction_data(self, cr, uid, ids, context=None):
|
||||
""" Return for each menu entry of ids :
|
||||
- if it uses the needaction mechanism (needaction_enabled)
|
||||
- the needaction counter of the related action, taking into account
|
||||
the action domain
|
||||
"""
|
||||
res = {}
|
||||
for menu in self.browse(cr, uid, ids, context=context):
|
||||
res[menu.id] = {
|
||||
'needaction_enabled': False,
|
||||
'needaction_counter': False,
|
||||
}
|
||||
if menu.action and menu.action.type in ('ir.actions.act_window','ir.actions.client') and menu.action.res_model:
|
||||
if menu.action and menu.action.type in ('ir.actions.act_window', 'ir.actions.client') and menu.action.res_model:
|
||||
obj = self.pool.get(menu.action.res_model)
|
||||
if obj and obj._needaction:
|
||||
if menu.action.type=='ir.actions.act_window':
|
||||
if menu.action.type == 'ir.actions.act_window':
|
||||
dom = menu.action.domain and eval(menu.action.domain, {'uid': uid}) or []
|
||||
else:
|
||||
dom = eval(menu.action.params_store or '{}', {'uid': uid}).get('domain')
|
||||
@@ -286,8 +318,10 @@ class ir_ui_menu(osv.osv):
|
||||
_columns = {
|
||||
'name': fields.char('Menu', size=64, required=True, translate=True),
|
||||
'sequence': fields.integer('Sequence'),
|
||||
'child_id' : fields.one2many('ir.ui.menu', 'parent_id','Child IDs'),
|
||||
'parent_id': fields.many2one('ir.ui.menu', 'Parent Menu', select=True),
|
||||
'child_id': fields.one2many('ir.ui.menu', 'parent_id', 'Child IDs'),
|
||||
'parent_id': fields.many2one('ir.ui.menu', 'Parent Menu', select=True, ondelete="restrict"),
|
||||
'parent_left': fields.integer('Parent Left', select=True),
|
||||
'parent_right': fields.integer('Parent Right', select=True),
|
||||
'groups_id': fields.many2many('res.groups', 'ir_ui_menu_group_rel',
|
||||
'menu_id', 'gid', 'Groups', help="If you have groups, the visibility of this menu will be based on these groups. "\
|
||||
"If this field is empty, OpenERP will compute visibility based on the related object's read access."),
|
||||
@@ -296,11 +330,14 @@ class ir_ui_menu(osv.osv):
|
||||
'icon': fields.selection(tools.icons, 'Icon', size=64),
|
||||
'icon_pict': fields.function(_get_icon_pict, type='char', size=32),
|
||||
'web_icon': fields.char('Web Icon File', size=128),
|
||||
'web_icon_hover':fields.char('Web Icon File (hover)', size=128),
|
||||
'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'),
|
||||
'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_enabled,
|
||||
type='boolean',
|
||||
store=True,
|
||||
string='Target model uses the need action mechanism',
|
||||
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.'),
|
||||
'action': fields.function(_action, fnct_inv=_action_inv,
|
||||
type='reference', string='Action',
|
||||
selection=[
|
||||
@@ -317,13 +354,14 @@ class ir_ui_menu(osv.osv):
|
||||
return _('Error ! You can not create recursive Menu.')
|
||||
|
||||
_constraints = [
|
||||
(osv.osv._check_recursion, _rec_message , ['parent_id'])
|
||||
(osv.osv._check_recursion, _rec_message, ['parent_id'])
|
||||
]
|
||||
_defaults = {
|
||||
'icon' : 'STOCK_OPEN',
|
||||
'icon_pict': ('stock', ('STOCK_OPEN','ICON_SIZE_MENU')),
|
||||
'sequence' : 10,
|
||||
'icon': 'STOCK_OPEN',
|
||||
'icon_pict': ('stock', ('STOCK_OPEN', 'ICON_SIZE_MENU')),
|
||||
'sequence': 10,
|
||||
}
|
||||
_order = "sequence,id"
|
||||
_parent_store = True
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -19,14 +19,15 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from osv import fields,osv
|
||||
from lxml import etree
|
||||
from tools import graph
|
||||
from tools.safe_eval import safe_eval as eval
|
||||
import tools
|
||||
from tools.view_validation import valid_view
|
||||
import os
|
||||
import logging
|
||||
from lxml import etree
|
||||
import os
|
||||
|
||||
from openerp import tools
|
||||
from openerp.osv import fields,osv
|
||||
from openerp.tools import graph
|
||||
from openerp.tools.safe_eval import safe_eval as eval
|
||||
from openerp.tools.view_validation import valid_view
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -254,7 +255,7 @@ class view(osv.osv):
|
||||
if label:
|
||||
for lbl in eval(label):
|
||||
if t.has_key(tools.ustr(lbl)) and tools.ustr(t[lbl])=='False':
|
||||
label_string = label_string + ' '
|
||||
label_string += ' '
|
||||
else:
|
||||
label_string = label_string + " " + tools.ustr(t[lbl])
|
||||
labels[str(t['id'])] = (a['id'],label_string)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user