[MERGE] Sync with trunk

bzr revid: tde@openerp.com-20130808083426-46cvo5e1g3x8nvgs
This commit is contained in:
Thibault Delavallée 2013-08-08 10:34:26 +02:00
commit 577f56497b
95 changed files with 8280 additions and 676 deletions

View File

@ -6,6 +6,14 @@ Changelog
`trunk`
-------
- Cleaned and slightly refactored ``ir.actions.server``. The ``loop``, ``sms``
and ``dummy`` server actions have been removed; ``object_create`` and
``object_copy`` have been merged into ``object_create``; ``other`` is now ``multi``
and raises in case of loops. See :ref:`ir-actions-server` for more details.
- Removed ``sms_send`` method.
- Added checking of recursions in many2many loops using ``_check_m2m_recursion``.
- Added MONTHS attribute on fields.date and fields.datetime, holding the list
(month_number, month_name)
- Almost removed ``LocalService()``. For reports,
``openerp.osv.orm.Model.print_report()`` can be used. For workflows, see
:ref:`orm-workflows`.

View File

@ -14,11 +14,13 @@ OpenERP Server
02_architecture
03_module_dev
04_security
workflows
05_test_framework
06_misc
deployment-gunicorn
deployment-mod-wsgi
form-view-guidelines
ir_actions
OpenERP Command
'''''''''''''''

48
doc/ir_actions.rst Normal file
View File

@ -0,0 +1,48 @@
.. _ir-actions:
Ir Actions
===========
.. _ir-actions-server:
Server actions
++++++++++++++
.. versionchanged:: 8.0
.. currentmodule:: openerp.addons.base.ir.ir_actions
.. autoclass:: actions_server
:noindex:
Adding a new sever action
-------------------------
The ``state`` field holds the various available types of server action. In order
to add a new server action, the first thing to do is to override the ``_get_states``
method that returns the list of values available for the selection field.
.. automethod:: actions_server._get_states
:noindex:
The method called when executing the server action is the ``run`` method. This
method calls ``run_action_<STATE>``. When adding a new server action type, you
have to define the related method that will be called upon execution.
.. automethod:: actions_server.run
:noindex:
Changelog
---------
`8.0`
'''''
The refactoring of OpenERP 8.0 server actions removed the following types of
server action:
- ``loop``: can be replaced by a ``code`` action
- ``dummy``: can be replaced by a void ``code`` action
- ``object_create`` and ``object_copy`` have been merged into a single and
more understandable ``object_create`` action
- ``other`` is renamed ``multi`` and raises in case of loops

306
doc/workflows.rst Normal file
View File

@ -0,0 +1,306 @@
.. _workflows:
Workflows
=========
In OpenERP, a workflow is a technical artefact to manage a set of "things to do"
associated to the records of some data model. The workflow provides a higher-
level way to organize the things to do on a record.
More specifically, a workflow is a directed graph where the nodes are called
"activities" and the arcs are called "transitions".
- Activities define work that should be done within the OpenERP server, such as
changing the state of some records, or sending emails.
- Transitions control how the workflow progresses from activity to activity.
In the definition of a workflow, one can attach conditions, signals, and
triggers to transitions, so that the behavior of the workflow depends on user
actions (such as clicking on a button), changes to records, or arbitrary Python
code.
Basics
------
Defining a workflow with data files is straightforward: a record "workflow" is
given together with records for the activities and the transitions. For
instance, here is a simple sequence of two activities defined in XML::
<record id="test_workflow" model="workflow">
<field name="name">test.workflow</field>
<field name="osv">test.workflow.model</field>
<field name="on_create">True</field>
</record>
<record id="activity_a" model="workflow.activity">
<field name="wkf_id" ref="test_workflow"/>
<field name="flow_start">True</field>
<field name="name">a</field>
<field name="kind">function</field>
<field name="action">print_a()</field>
</record>
<record id="activity_b" model="workflow.activity">
<field name="wkf_id" ref="test_workflow"/>
<field name="flow_stop">True</field>
<field name="name">b</field>
<field name="kind">function</field>
<field name="action">print_b()</field>
</record>
<record id="trans_a_b" model="workflow.transition">
<field name="act_from" ref="activity_a"/>
<field name="act_to" ref="activity_b"/>
</record>
A worfklow is always defined with respect to a particular model (the model is
given by the attribute ``osv`` on the model ``workflow``). Methods specified in
the activities or transitions will be called on that model.
In the example code above, a workflow called "test_workflow" is created. It is
made up of two activies, named "a" and "b", and one transition, going from "a"
to "b".
The first activity has its attribute ``flow_start`` set to ``True`` so that
OpenERP knows where to start the workflow traversal after it is instanciated.
Because ``on_create`` is set to True on the workflow record, the workflow is
instanciated for each newly created record. (Otherwise, the workflow should be
instanciated by other means, such as from some module Python code.)
When the workflow is instanciated, it begins with activity "a". That activity
is of kind ``function``, which means that the action ``print_a()`` is a method
call on the model ``test.workflow`` (the usual ``cr, uid, ids, context``
arguments are passed for you).
The transition between "a" and "b" does not specify any condition. This means
that the workflow instance immediately goes from "a" to "b" after "a" has been
processed, and thus also processes activity "b".
Transitions
-----------
Transitions provide the control structures to orchestrate a workflow. When an
activity is completed, the workflow engine tries to get across transitions
departing from the completed activity, towards the next activities. In their
simplest form (as in the example above), they link activities sequentially:
activities are processed as soon as the activities preceding them are completed.
Instead of running all activities in one fell swoop, it is also possible to wait
on transitions, going through them only when some criteria are met. The criteria
are the conditions, the signals, and the triggers. They are detailed in the
following sections.
Conditions
''''''''''
When an activity has been completed, its outgoing transitions are inspected to
determine whether it is possible for the workflow instance to proceed through
them and reach the next activities. When only a condition is defined (i.e., no
signal or trigger is defined), the condition is evaluated by OpenERP, and if it
evaluates to ``True``, the worklfow instance progresses through the transition.
If the condition is not met, it will be reevaluated every time the associated
record is modified, or by an explicit method call to do it.
By default, the attribute ``condition`` (i.e., the expression to be evaluated)
is just "True", which trivially evaluates to ``True``. Note that the condition
may be several lines long; in that case, the value of the last one determines
whether the transition can be taken.
In the condition evaluation environment, several symbols are conveniently
defined (in addition to the OpenERP ``safe_eval`` environment):
- all the model column names, and
- all the browse record's attributes.
Signals
'''''''
In addition to a condition, a transition can specify a signal name. When such
a signal name is present, the transition is not taken directly, even if the
condition evaluates to ``True``. Instead the transition blocks, waiting to be
woken up.
In order to wake up a transition with a defined signal name, the signal must be
sent to the workflow instance. A common way to send a signal is to use a button
in the user interface, using the element ``<button/>`` with the signal name as
the attribute ``name`` of the button. Once the button is clicked, the signal is
sent to the workflow instance of the current record.
.. note:: The condition is still evaluated when the signal is sent to the
workflow instance.
Triggers
''''''''
With conditions that evaluate to ``False``, transitions are not taken (and thus
the activity it leads to is not processed immediately). Still, the workflow
instance can get new chances to progress across that transition by providing
so-called triggers. The idea is that when the condition is not satisfied,
triggers are recorded in database. Later, it is possible to wake up
specifically the workflow instances that installed those triggers, offering
them to reevaluate their transition conditions. This mechanism makes it cheaper
to wake up workflow instances by targetting just a few of them (those that have
installed the triggers) instead of all of them.
Triggers are recorded in database as record IDs (together with the model name)
and refer to the workflow instance waiting for those records. The transition
definition provides a model name (attribute ``trigger_model``) and a Python
expression (attribute ``trigger_expression``) that evaluates to a list of record
IDs in the given model. Any of those records can wake up the workflow instance
they are associated with.
.. note:: Note that triggers are not re-installed whenever the transition is
re-tried.
Splitting and joining transitions
'''''''''''''''''''''''''''''''''
When multiple transitions leave the same activity, or lead to the same activity,
OpenERP provides some control over which transitions are actually taken, or how
the reached activity will be processed. The attributes ``split_mode`` and
``join_mode`` on the activity are used for such control. The possible values of
those attributes are explained below.
Activities
----------
While the transitions can be seen as the control structures of the workflows,
activities are the places where everything happens, from changing record states
to sending email.
Different kinds of activities exist: ``Dummy``, ``Function``, ``Subflow``, and
``Stop all``, each doing different things when the activity is processed. In
addition to their kind, activies have other properties, detailed in the next
sections.
Flow start and flow stop
''''''''''''''''''''''''
The attribute ``flow_start`` is a boolean value specifying whether the activity
is processed when the workflow is instanciated. Multiple activities can have
their attribute ``flow_start`` set to ``True``. When instanciating a workflow
for a record, OpenERP simply processes all of them, and evaluate all their
outgoing transitions afterwards.
The attribute ``flow_stop`` is a boolean value specifying whether the activity
stops the workflow instance. A workflow instance is considered completed when
all its activities with the attribute ``flow_stop`` set to ``True`` are
completed.
It is important for OpenERP to know when a workflow instance is completed. A
workflow can have an activity that is actually another workflow (called a
subflow); that activity is completed when the subflow is completed.
Subflow
'''''''
An activity can embed a complete workflow, called a subflow (the embedding
workflow is called the parent workflow). The workflow to instanciate is
specified by attribute ``subflow_id``.
.. note:: In the GUI, that attribute can not be set unless the kind of the
activity is ``Subflow``.
The activity is considered completed (and its outgoing transitions ready to be
evaluated) when the subflow is completed (see attribute ``flow_stop`` above).
Sending a signal from a subflow
'''''''''''''''''''''''''''''''
When a workflow is embedded in an activity (as a subflow) of a workflow, the
sublow can send a signal from its own activities to the parent workflow by
giving a signal name in the attribute ``signal_send``. OpenERP processes those
activities by sending the value of ``signal_send`` prefixed by "subflow." to
the parent workflow instance.
In other words, it is possible to react and get transitions in the parent
workflow as activities are executed in the sublow.
Server actions
''''''''''''''
An activity can run a "Server Action" by specifying its ID in the attribute
``action_id``.
Python action
'''''''''''''
An activity can execute some Python code, given by the attribute ``action``.
The evaluation environment is the same as the one explained in the section
`Conditions`_.
Split mode
''''''''''
After an activity has been processed, its outgoing transitions are evaluated.
Normally, if a transition can be taken, OpenERP traverses it and proceed to the
activity the transition leads to.
Actually, when more than a single transition is leaving an activity, OpenERP may
proceed or not, depending on the other transitions. That is, the conditions on
the transitions can be combined together, and the combined result instructs
OpenERP to traverse zero, one, or all the transitions. The way they are combined
is controlled by the attribute ``split_mode``.
There are three possible split modes: ``XOR``, ``OR`` and ``AND``.
``XOR``
When the transitions are combined with a ``XOR`` split mode, as soon as a
transition has a satisfied condition, the transition is traversed and the
others are skipped.
``OR``
With the ``OR`` mode, all the transitions with a satisfied condition are
traversed. The remaining transitions will not be evaluated later.
``AND``
With the ``AND`` mode, OpenERP will wait for all outgoing transition
conditions to be satisfied, then traverse all of them at once.
Join mode
'''''''''
Just like outgoing transition conditions can be combined together to decide
whether they can be traversed or not, incoming transitions can be combined
together to decide if and when an activity may be processed. The attribute
``join_mode`` controls that behavior.
There are two possible join modes: ``XOR`` and ``AND``.
``XOR``
With the ``XOR`` mode, an incoming transition with a satisfied condition is
traversed immediately, and enables the processing of the activity.
``AND``
With the ``AND`` mode, OpenERP will wait until all incoming transitions have
been traversed before enabling the processing of the activity.
Kinds
'''''
Activities can be of different kinds: ``dummy``, ``function``, ``subflow``, or
``stopall``. The kind defines what type of work an activity can do.
Dummy
The ``dummy`` kind is for activities that do nothing, or for activities that
only call a server action. Activities that do nothing can be used as hubs to
gather/dispatch transitions.
Function
The ``function`` kind is for activities that only need to run some Python
code, and possibly a server action.
Stop all
The ``stopall`` kind is for activities that will completely stop the
workflow instance and mark it as completed. In addition they can also run
some Python code.
Subflow
When the kind of the activity is ``subflow``, the activity embeds another
workflow instance. When the subflow is completed, the activity is also
considered completed.
By default, the subflow is instanciated for the same record as the parent
workflow. It is possible to change that behavior by providing Python code
that returns a record ID (of the same data model as the subflow). The
embedded subflow instance is then the one of the given record.

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:42+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:44+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:42+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:44+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:43+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:44+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:43+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:44+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:43+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:45+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:43+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:45+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:43+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:45+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:44+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:45+0000\n"
"X-Generator: Launchpad (build 16718)\n"
"X-Poedit-Language: Czech\n"
#. module: base
@ -4897,7 +4897,7 @@ msgstr "Objednávka obědů, stravování, potraviny"
#: field:ir.model.fields,required:0
#: field:res.partner.bank.type.field,required:0
msgid "Required"
msgstr "Požadováno"
msgstr "Povinné"
#. module: base
#: model:res.country,name:base.ro
@ -7934,7 +7934,7 @@ msgstr "Zleva doprava"
#: view:res.lang:0
#: field:res.lang,translatable:0
msgid "Translatable"
msgstr "Přeložitelný"
msgstr "Přeložitelné"
#. module: base
#: help:base.language.import,code:0
@ -14735,7 +14735,7 @@ msgstr ""
#: field:ir.actions.server,trigger_obj_id:0
#: field:ir.model.fields,relation_field:0
msgid "Relation Field"
msgstr "?!?Související pole"
msgstr "Pole relace"
#. module: base
#: model:ir.module.module,description:base.module_portal_project
@ -15029,7 +15029,7 @@ msgstr ""
#. module: base
#: field:ir.model.fields,relation:0
msgid "Object Relation"
msgstr "Vztah objektu"
msgstr "Relace objektu"
#. module: base
#: model:ir.module.module,shortdesc:base.module_account_voucher

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:44+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:45+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:45+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:46+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing
@ -8452,15 +8452,15 @@ msgstr ""
"\n"
"Jeder Mitarbeiter kann seine aufgewendete Zeit für verschiedene Projekte "
"kodieren und verfolgen.\n"
"Ein Projekt ist eine analytische Rechnung und die Zeit, die jemand an diesem "
"Projekt verbracht hat,\n"
"verursacht Kosten auf dem entsprechenden Konto. \n"
"Ein Projekt wird automatisch zur Kostenstelle. Die Zeit, die jemand an "
"diesem Projekt verbracht hat,\n"
"verursacht dementsprechend Kosten auf dieser Projektkostenstelle. \n"
"\n"
"Viele Berichterstattungen über die Verfolgung von Zeit und Mitarbeiter "
"werden bereitgestellt.\n"
"Es ist vollständig in die Kostenrechnung integriert. Es ermöglicht Ihnen die "
"Einrichtung einer \n"
"Betriebsführung nach Angelegenheit.\n"
"Das Modul bietet außerdem Auswertungen zur Rückverfolgung der Mitarbeiter "
"Arbeitszeit in Projekten.\n"
"Es ist dadurch vollständig in die OpenERP Kostenrechnung integriert und "
"ermöglicht dem Management \n"
"ein effizientes Projektcontrolling.\n"
" "
#. module: base

View File

@ -12,8 +12,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:45+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:47+0000\n"
"X-Generator: Launchpad (build 16718)\n"
"X-Poedit-Country: GREECE\n"
"X-Poedit-Language: Greek\n"
"X-Poedit-SourceCharset: utf-8\n"

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:51+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:53+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing
@ -25,6 +25,10 @@ msgid ""
"================================================\n"
" "
msgstr ""
"\n"
"Module for the Check Writing and Check Printing.\n"
"================================================\n"
" "
#. module: base
#: model:res.country,name:base.sh
@ -60,7 +64,7 @@ msgstr "View Architecture"
#. module: base
#: model:ir.module.module,summary:base.module_sale_stock
msgid "Quotation, Sale Orders, Delivery & Invoicing Control"
msgstr ""
msgstr "Quotation, Sale Orders, Delivery & Invoicing Control"
#. module: base
#: selection:ir.sequence,implementation:0
@ -89,12 +93,12 @@ msgstr ""
#. module: base
#: model:ir.module.module,summary:base.module_point_of_sale
msgid "Touchscreen Interface for Shops"
msgstr ""
msgstr "Touchscreen Interface for Shops"
#. module: base
#: model:ir.module.module,shortdesc:base.module_l10n_in_hr_payroll
msgid "Indian Payroll"
msgstr ""
msgstr "Indian Payroll"
#. module: base
#: help:ir.cron,model:0
@ -138,7 +142,7 @@ msgstr ""
#. module: base
#: field:ir.actions.client,params:0
msgid "Supplementary arguments"
msgstr ""
msgstr "Supplementary arguments"
#. module: base
#: model:ir.module.module,description:base.module_google_base_account
@ -147,11 +151,14 @@ msgid ""
"The module adds google user in res user.\n"
"========================================\n"
msgstr ""
"\n"
"The module adds google user in res user.\n"
"========================================\n"
#. module: base
#: help:res.partner,employee:0
msgid "Check this box if this contact is an Employee."
msgstr ""
msgstr "Check this box if this contact is an Employee."
#. module: base
#: help:ir.model.fields,domain:0
@ -182,7 +189,7 @@ msgstr "Target Window"
#. module: base
#: field:ir.actions.report.xml,report_rml:0
msgid "Main Report File Path"
msgstr ""
msgstr "Main Report File Path"
#. module: base
#: model:ir.module.module,shortdesc:base.module_sale_analytic_plans
@ -203,6 +210,16 @@ msgid ""
"revenue\n"
"reports."
msgstr ""
"\n"
"Generate your Invoices from Expenses, Timesheet Entries.\n"
"========================================================\n"
"\n"
"Module to generate invoices based on costs (human resources, expenses, "
"...).\n"
"\n"
"You can define price lists in analytic account, make some theoretical "
"revenue\n"
"reports."
#. module: base
#: code:addons/base/ir/ir_sequence.py:134

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:49+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:51+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing
@ -23520,3 +23520,115 @@ msgstr "Registro"
#~ "que hacer, y se puede concentrar en la realización efectiva de dichas "
#~ "tareas.\n"
#~ " "
#~ msgid ""
#~ "\n"
#~ "This module allows to use several analytic plans, according to the general "
#~ "journal.\n"
#~ "============================================================================="
#~ "======\n"
#~ "\n"
#~ "Here multiple analytic lines are created when the invoice or the entries\n"
#~ "are confirmed.\n"
#~ "\n"
#~ "For example, you can define the following analytic structure:\n"
#~ " Projects\n"
#~ " Project 1\n"
#~ " SubProj 1.1\n"
#~ " SubProj 1.2\n"
#~ "\n"
#~ " Project 2\n"
#~ " Salesman\n"
#~ " Eric\n"
#~ " Fabien\n"
#~ "\n"
#~ "Here, we have two plans: Projects and Salesman. An invoice line must\n"
#~ "be able to write analytic entries in the 2 plans: SubProj 1.1 and\n"
#~ "Fabien. The amount can also be split. The following example is for\n"
#~ "an invoice that touches the two subproject and assigned to one salesman:\n"
#~ "\n"
#~ "Plan1:\n"
#~ " SubProject 1.1 : 50%\n"
#~ " SubProject 1.2 : 50%\n"
#~ "Plan2:\n"
#~ " Eric: 100%\n"
#~ "\n"
#~ "So when this line of invoice will be confirmed, it will generate 3 analytic "
#~ "lines,\n"
#~ "for one account entry.\n"
#~ "The analytic plan validates the minimum and maximum percentage at the time "
#~ "of creation\n"
#~ "of distribution models.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ "Este módulo permite usar varios planes analíticos, según el diario general.\n"
#~ "============================================================================="
#~ "======\n"
#~ "\n"
#~ "Se crean varias líneas analíticas cuando la factura o las entradas se "
#~ "confirman.\n"
#~ "\n"
#~ "Por ejemplo, se puede definir la siguiente estructura analítica:\n"
#~ " Proyectos\n"
#~ " Proyecto 1\n"
#~ " SubProyecto 1.1\n"
#~ " SubProyecto 1.2\n"
#~ " Proyecto 2\n"
#~ "\n"
#~ " Comercial\n"
#~ " Eric\n"
#~ " Fabien\n"
#~ "\n"
#~ "Como vemos, hay dos planes: Proyectos y Comercial.\n"
#~ "Una lñinea de factura debe ser capaz de escribir entradas analíticas\n"
#~ "en dos planes: SubProyecto 1.1 y Fabien. También se puede repartir la "
#~ "cantidad.\n"
#~ "\n"
#~ "El siguiente ejemplo es para una factura que afecta a los dos subproyectos\n"
#~ "y se asigna a uno de los comerciales:\n"
#~ "\n"
#~ "Plan1:\n"
#~ " SubProyecto 1.1 : 50%\n"
#~ " SubProyecto 1.2 : 50%\n"
#~ "Plan2:\n"
#~ " Eric: 100%\n"
#~ "\n"
#~ "Así, cuando se confirme la línea de factura, se generarán 3 líneas "
#~ "analíticas,\n"
#~ "para un asiento contable.\n"
#~ "El plan analítico valida los porcentajes mínimo y máximo en el momento de "
#~ "creación\n"
#~ "de los modelos distribuidos.\n"
#~ " "
#~ msgid ""
#~ "\n"
#~ "This module allows you to produce several products from one production "
#~ "order.\n"
#~ "============================================================================="
#~ "\n"
#~ "\n"
#~ "You can configure sub-products in the bill of material.\n"
#~ "\n"
#~ "Without this module:\n"
#~ " A + B + C -> D\n"
#~ "\n"
#~ "With this module:\n"
#~ " A + B + C -> D + E\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ "Este módulo permite producir varios productos desde una única orden de "
#~ "producción.\n"
#~ "============================================================================="
#~ "\n"
#~ "\n"
#~ "Se pueden configurar subproductos en la lista de materiales.\n"
#~ "\n"
#~ "Sin este módulo:\n"
#~ " A + B + C -> D\n"
#~ "\n"
#~ "Con este módulo:\n"
#~ " A + B + C -> D + E\n"
#~ " "

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:51+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:53+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:52+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:53+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:52+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:54+0000\n"
"X-Generator: Launchpad (build 16718)\n"
"Language: \n"
#. module: base
@ -191,7 +191,7 @@ msgstr "Ventana destino"
#. module: base
#: field:ir.actions.report.xml,report_rml:0
msgid "Main Report File Path"
msgstr ""
msgstr "Ruta de archivo principal del informe"
#. module: base
#: model:ir.module.module,shortdesc:base.module_sale_analytic_plans
@ -212,6 +212,15 @@ msgid ""
"revenue\n"
"reports."
msgstr ""
"\n"
"Genere facturas desde los gastos y partes de horas.\n"
"========================================================\n"
"\n"
"Módulo para generar facturas basadas en los costes (recursos humanos, "
"gastos, ...).\n"
"\n"
"Puede definir tarifas en la contabilidad analítica, realizando algún informe "
"teórico de beneficios."
#. module: base
#: code:addons/base/ir/ir_sequence.py:134
@ -263,7 +272,7 @@ msgstr "creado."
#. module: base
#: field:ir.actions.report.xml,report_xsl:0
msgid "XSL Path"
msgstr ""
msgstr "Ruta XSL"
#. module: base
#: model:ir.module.module,shortdesc:base.module_l10n_tr
@ -289,7 +298,7 @@ msgstr "Inuktitut / ᐃᓄᒃᑎᑐᑦ"
#. module: base
#: model:res.groups,name:base.group_multi_currency
msgid "Multi Currencies"
msgstr ""
msgstr "Multidivisa"
#. module: base
#: model:ir.module.module,description:base.module_l10n_cl
@ -301,6 +310,12 @@ msgid ""
"\n"
" "
msgstr ""
"\n"
"Plan de cuentas y localización de impuestos chilenos.\n"
"==============================================\n"
"Plan contable chileno e impuestos de acuerdo a disposiciones vigentes\n"
"\n"
" "
#. module: base
#: model:ir.module.module,shortdesc:base.module_sale
@ -313,6 +328,7 @@ msgid ""
"The internal user that is in charge of communicating with this contact if "
"any."
msgstr ""
"El usuario interno encargado de comunicarse con este contacto si lo hubiese."
#. module: base
#: view:res.partner:0

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:51+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:53+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:53+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:54+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -8,14 +8,15 @@ msgstr ""
"Project-Id-Version: openobject-server\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:04+0000\n"
"PO-Revision-Date: 2012-09-07 01:11+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"PO-Revision-Date: 2013-07-25 03:08+0000\n"
"Last-Translator: Federico Manuel Echeverri Choux - ( Vauxoo ) "
"<echeverrifm@gmail.com>\n"
"Language-Team: Spanish (Mexico) <es_MX@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:53+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:54+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing
@ -613,7 +614,7 @@ msgstr ""
#. module: base
#: field:res.country,name:0
msgid "Country Name"
msgstr ""
msgstr "Nombre del país"
#. module: base
#: model:res.country,name:base.co
@ -14300,7 +14301,7 @@ msgstr ""
#: field:res.partner,vat:0
#, python-format
msgid "TIN"
msgstr ""
msgstr "RFC"
#. module: base
#: model:res.country,name:base.aw
@ -14316,7 +14317,7 @@ msgstr ""
#. module: base
#: model:res.country,name:base.ar
msgid "Argentina"
msgstr ""
msgstr "Argentina"
#. module: base
#: field:res.groups,full_name:0
@ -14357,7 +14358,7 @@ msgstr ""
#. module: base
#: model:ir.module.category,name:base.module_category_report_designer
msgid "Advanced Reporting"
msgstr ""
msgstr "Informes avanzados"
#. module: base
#: model:ir.module.module,summary:base.module_purchase
@ -14405,7 +14406,7 @@ msgstr ""
#. module: base
#: model:ir.module.module,shortdesc:base.module_l10n_fr
msgid "France - Accounting"
msgstr ""
msgstr "Francia - Contabilidad"
#. module: base
#: view:ir.actions.todo:0
@ -14549,7 +14550,7 @@ msgstr ""
#. module: base
#: selection:base.language.install,lang:0
msgid "Czech / Čeština"
msgstr ""
msgstr "Checo / Čeština"
#. module: base
#: model:ir.module.category,name:base.module_category_generic_modules
@ -14608,7 +14609,7 @@ msgstr ""
#: model:ir.module.category,name:base.module_category_hidden
#: view:res.users:0
msgid "Technical Settings"
msgstr ""
msgstr "Configuración técnica"
#. module: base
#: model:ir.module.category,description:base.module_category_accounting_and_finance
@ -14616,6 +14617,8 @@ msgid ""
"Helps you handle your accounting needs, if you are not an accountant, we "
"suggest you to install only the Invoicing."
msgstr ""
"Le ayuda a manejar sus necesidades contables. Si no es un contable, le "
"recomendamos que sólo instale el módulo 'invoicing'."
#. module: base
#: model:ir.module.module,shortdesc:base.module_plugin_thunderbird
@ -14625,7 +14628,7 @@ msgstr ""
#. module: base
#: model:ir.module.module,summary:base.module_event
msgid "Trainings, Conferences, Meetings, Exhibitions, Registrations"
msgstr ""
msgstr "Formaciones, conferencias, reuniones, exhibiciones, inscripciones"
#. module: base
#: model:ir.model,name:base.model_res_country
@ -14688,7 +14691,7 @@ msgstr ""
#. module: base
#: model:ir.module.module,shortdesc:base.module_l10n_nl
msgid "Netherlands - Accounting"
msgstr ""
msgstr "Holanda - Contabilidad"
#. module: base
#: model:res.country,name:base.gs
@ -14713,6 +14716,11 @@ msgid ""
"1,06,500;[1,2,-1] will represent it to be 106,50,0;[3] will represent it as "
"106,500. Provided ',' as the thousand separator in each case."
msgstr ""
"El formato de separación debería ser como [,n] dónde 0 < n, empezando por "
"el dígito unidad. -1 terminará la separación. Por ej. [3,2,-1] representará "
"106500 como 1,06,500; [1,2,-1] lo representará como 106,50,0; [3] lo "
"representará como 106,500. Siempre que ',' sea el separador de mil en cada "
"caso."
#. module: base
#: field:ir.module.module,auto_install:0
@ -14732,6 +14740,12 @@ msgid ""
"taxes\n"
"and the Lempira currency."
msgstr ""
"\n"
"Éste es el módulo base para gestionar el plan de cuentas para Honduras.\n"
"====================================================================\n"
" \n"
"Agrega una nomenclatura contable para Honduras. También incluye impuestos y "
"la moneda Lempira."
#. module: base
#: model:res.country,name:base.jp
@ -14798,12 +14812,12 @@ msgstr ""
#. module: base
#: model:ir.module.module,shortdesc:base.module_l10n_ca
msgid "Canada - Accounting"
msgstr ""
msgstr "Canada - Contabilidad"
#. module: base
#: model:ir.module.module,shortdesc:base.module_l10n_co
msgid "Colombian - Accounting"
msgstr ""
msgstr "Contabilidad colombiana"
#. module: base
#: model:ir.module.module,description:base.module_account_voucher
@ -14870,7 +14884,7 @@ msgstr ""
#. module: base
#: model:ir.module.module,shortdesc:base.module_l10n_ve
msgid "Venezuela - Accounting"
msgstr ""
msgstr "Venezuela - Contabilidad"
#. module: base
#: model:res.country,name:base.cl

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:51+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:53+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:44+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:46+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:43+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:44+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -9,8 +9,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:48+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:49+0000\n"
"X-Generator: Launchpad (build 16718)\n"
"X-Poedit-Country: IRAN, ISLAMIC REPUBLIC OF\n"
"X-Poedit-Language: Persian\n"

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:53+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:54+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:44+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:46+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:44+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:46+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing
@ -8891,7 +8891,7 @@ msgstr "Sur plusieurs documents"
#: view:res.users:0
#: view:wizard.ir.model.menu.create:0
msgid "or"
msgstr ""
msgstr "ou"
#. module: base
#: model:ir.module.module,shortdesc:base.module_account_accountant
@ -9591,7 +9591,7 @@ msgstr ""
" </p><p>\n"
" OpenERP vous aide à suivre toutes les activités avec un "
"client : \n"
" les discussions, l'historiques des opportunités, \n"
" les discussions, l'historique des opportunités, \n"
" les documents, etc.\n"
" </p>\n"
" "
@ -10040,7 +10040,7 @@ msgstr "Recherche toujours possible"
#. module: base
#: help:res.country.state,code:0
msgid "The state code in max. three chars."
msgstr ""
msgstr "Code de l'état, avec au plus trois caractères."
#. module: base
#: model:res.country,name:base.hk
@ -10976,7 +10976,7 @@ msgstr "Mali"
#. module: base
#: model:ir.ui.menu,name:base.menu_project_config_project
msgid "Stages"
msgstr ""
msgstr "Étapes"
#. module: base
#: selection:base.language.install,lang:0
@ -12836,7 +12836,7 @@ msgstr ""
#: model:ir.actions.act_window,name:base.action_inventory_form
#: model:ir.ui.menu,name:base.menu_action_inventory_form
msgid "Default Company per Object"
msgstr ""
msgstr "Société par défaut par objet"
#. module: base
#: model:ir.module.module,shortdesc:base.module_web_hello
@ -13383,7 +13383,7 @@ msgstr ""
#. module: base
#: model:ir.model,name:base.model_ir_fields_converter
msgid "ir.fields.converter"
msgstr ""
msgstr "ir.fields.converter"
#. module: base
#: code:addons/base/res/res_partner.py:439
@ -13411,7 +13411,7 @@ msgstr "Annuler l'installation"
#. module: base
#: model:ir.model,name:base.model_ir_model_relation
msgid "ir.model.relation"
msgstr ""
msgstr "ir.model.relation"
#. module: base
#: model:ir.module.module,shortdesc:base.module_account_check_writing
@ -14294,7 +14294,7 @@ msgstr "Banque"
#: model:ir.module.category,name:base.module_category_point_of_sale
#: model:ir.module.module,shortdesc:base.module_point_of_sale
msgid "Point of Sale"
msgstr ""
msgstr "Point de vente"
#. module: base
#: model:ir.module.module,description:base.module_mail
@ -15202,7 +15202,7 @@ msgstr ""
#. module: base
#: view:res.partner:0
msgid "Fax:"
msgstr ""
msgstr "Fax :"
#. module: base
#: selection:ir.ui.view,type:0
@ -15968,7 +15968,7 @@ msgstr "Modules à mettre à jour"
#. module: base
#: model:ir.ui.menu,name:base.menu_custom_multicompany
msgid "Multi-Companies"
msgstr ""
msgstr "Multi-sociétés"
#. module: base
#: field:workflow,osv:0
@ -16996,7 +16996,7 @@ msgstr ""
#. module: base
#: field:ir.attachment,file_size:0
msgid "File Size"
msgstr ""
msgstr "Taille du fichier"
#. module: base
#: help:ir.sequence,prefix:0
@ -17011,7 +17011,7 @@ msgstr "Seychelles"
#. module: base
#: model:res.partner.category,name:base.res_partner_category_4
msgid "Gold"
msgstr ""
msgstr "Or"
#. module: base
#: code:addons/base/res/res_company.py:173

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:52+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:53+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:45+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:47+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:45+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:47+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing
@ -29,12 +29,12 @@ msgstr ""
#. module: base
#: model:res.country,name:base.sh
msgid "Saint Helena"
msgstr ""
msgstr "સંત હેલેના"
#. module: base
#: view:ir.actions.report.xml:0
msgid "Other Configuration"
msgstr "બીજા કન્ફિગ્યુરેશન"
msgstr "અન્ય રેખાંકન"
#. module: base
#: selection:ir.property,type:0
@ -232,7 +232,7 @@ msgstr "સ્પાર્સ ફિલ્ડ \"%s\" નુ નામ બદલ
#. module: base
#: model:res.country,name:base.sz
msgid "Swaziland"
msgstr "સ્વાઝિલેન્ડ"
msgstr "સ્વાઝલેન્ડ"
#. module: base
#: code:addons/orm.py:4485

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:45+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:47+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:46+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:47+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:49+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:50+0000\n"
"X-Generator: Launchpad (build 16718)\n"
"Language: hr\n"
#. module: base

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:46+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:47+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -9,8 +9,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:43+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:44+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:46+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:48+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing
@ -42,7 +42,7 @@ msgstr "Konfigurasi Lainnya"
#. module: base
#: selection:ir.property,type:0
msgid "DateTime"
msgstr ""
msgstr "DateTime"
#. module: base
#: code:addons/fields.py:652
@ -51,6 +51,8 @@ msgid ""
"The second argument of the many2many field %s must be a SQL table !You used "
"%s, which is not a valid SQL table name."
msgstr ""
"Argumen kedua dari field many2many %s harus merupakan tabel SQL ! Anda "
"menggunakan %s, yang mana bukan table SQL."
#. module: base
#: field:ir.ui.view,arch:0
@ -66,7 +68,7 @@ msgstr "Kontrol Penawaran, Pesanan Penjualan, Pengiriman & Faktur"
#. module: base
#: selection:ir.sequence,implementation:0
msgid "No gap"
msgstr ""
msgstr "Tidak ada gap"
#. module: base
#: selection:base.language.install,lang:0
@ -76,7 +78,7 @@ msgstr "Bahasa Hungaria"
#. module: base
#: selection:base.language.install,lang:0
msgid "Spanish (PY) / Español (PY)"
msgstr ""
msgstr "Bahasa Spanyol"
#. module: base
#: model:ir.module.category,description:base.module_category_project_management
@ -84,27 +86,30 @@ msgid ""
"Helps you manage your projects and tasks by tracking them, generating "
"plannings, etc..."
msgstr ""
"Memungkinkan Anda untuk mengelola proyek dan tugas dengan cara melacaknya, "
"menghasilkan perencanaan dan lain sebagainya ..."
#. module: base
#: model:ir.module.module,summary:base.module_point_of_sale
msgid "Touchscreen Interface for Shops"
msgstr ""
msgstr "Antarmuka layar sentuh untuk digunakan di toko"
#. module: base
#: model:ir.module.module,shortdesc:base.module_l10n_in_hr_payroll
msgid "Indian Payroll"
msgstr ""
msgstr "Payroll untuk India"
#. module: base
#: help:ir.cron,model:0
msgid ""
"Model name on which the method to be called is located, e.g. 'res.partner'."
msgstr ""
"Sebutkan nama model yang mana metode ini akan dipanggil, misal 'res.partner'."
#. module: base
#: view:ir.module.module:0
msgid "Created Views"
msgstr "View Tercipta"
msgstr "View yang diciptakan"
#. module: base
#: model:ir.module.module,description:base.module_product_manufacturer
@ -121,11 +126,24 @@ msgid ""
" * Product Attributes\n"
" "
msgstr ""
"\n"
"Modul ini berfungsi untuk menambahkan nama produsen beserta atributnya pada "
"formulir produk.\n"
"============================================================================="
"==\n"
"\n"
"Anda dapat mengisi data-data berikut pada produk:\n"
"------------------------------------------------\n"
"* Nama produsen\n"
"* Nama produk produsen\n"
"* Code produk produsen\n"
"* Atribut produk\n"
" "
#. module: base
#: field:ir.actions.client,params:0
msgid "Supplementary arguments"
msgstr ""
msgstr "Argumen tambahan"
#. module: base
#: model:ir.module.module,description:base.module_google_base_account
@ -134,11 +152,14 @@ msgid ""
"The module adds google user in res user.\n"
"========================================\n"
msgstr ""
"\n"
"Modul ini akan menambahkan google user dalam obyek res.user\n"
"====================================================\n"
#. module: base
#: help:res.partner,employee:0
msgid "Check this box if this contact is an Employee."
msgstr ""
msgstr "Centang kotak ini jika kontak adalah pegawai"
#. module: base
#: help:ir.model.fields,domain:0
@ -147,6 +168,9 @@ msgid ""
"specified as a Python expression defining a list of triplets. For example: "
"[('color','=','red')]"
msgstr ""
"Sebuah domain (opsional) untuk membatasi nilai untuk field yang terelasi, "
"dimana merupakan ekspresi Python yang mendefinisikan daftar triplet. Sebagai "
"contoh: [('color', '=', 'merah')]"
#. module: base
#: field:res.partner,ref:0
@ -156,7 +180,7 @@ msgstr "Referensi"
#. module: base
#: model:ir.module.module,shortdesc:base.module_l10n_be_invoice_bba
msgid "Belgium - Structured Communication"
msgstr ""
msgstr "Belgia - Komunikasi terstruktur"
#. module: base
#: field:ir.actions.act_window,target:0
@ -166,12 +190,12 @@ msgstr "Jendela Sasaran"
#. module: base
#: field:ir.actions.report.xml,report_rml:0
msgid "Main Report File Path"
msgstr ""
msgstr "Path (lokasi) file laporan"
#. module: base
#: model:ir.module.module,shortdesc:base.module_sale_analytic_plans
msgid "Sales Analytic Distribution"
msgstr ""
msgstr "Untuk mendistribusikan data analisis pada proses Penjualan"
#. module: base
#: model:ir.module.module,description:base.module_hr_timesheet_invoice
@ -203,12 +227,14 @@ msgid ""
"Properties of base fields cannot be altered in this manner! Please modify "
"them through Python code, preferably through a custom addon!"
msgstr ""
"Sifat field basis tidak dapat diubah dengan cara ini! Silahkan "
"memodifikasinya melalui kode Python, sebaiknya dibuat modul terpisah."
#. module: base
#: code:addons/osv.py:151
#, python-format
msgid "Constraint Error"
msgstr ""
msgstr "Kesalahan"
#. module: base
#: model:ir.model,name:base.model_ir_ui_view_custom
@ -219,7 +245,7 @@ msgstr "ir.ui.view.custom"
#: code:addons/base/ir/ir_model.py:374
#, python-format
msgid "Renaming sparse field \"%s\" is not allowed"
msgstr ""
msgstr "Mengganti nama field \"%s\" tidak diperbolehkan"
#. module: base
#: model:res.country,name:base.sz
@ -235,12 +261,12 @@ msgstr "dibuat"
#. module: base
#: field:ir.actions.report.xml,report_xsl:0
msgid "XSL Path"
msgstr ""
msgstr "Lokasi file XSL"
#. module: base
#: model:ir.module.module,shortdesc:base.module_l10n_tr
msgid "Turkey - Accounting"
msgstr ""
msgstr "Turki - Akunting"
#. module: base
#: field:ir.sequence,number_increment:0
@ -256,12 +282,12 @@ msgstr "Struktur Perusahaan"
#. module: base
#: selection:base.language.install,lang:0
msgid "Inuktitut / ᐃᓄᒃᑎᑐᑦ"
msgstr ""
msgstr "Inuktitut / ᐃᓄᒃᑎᑐᑦ"
#. module: base
#: model:res.groups,name:base.group_multi_currency
msgid "Multi Currencies"
msgstr ""
msgstr "Multi Mata Uang"
#. module: base
#: model:ir.module.module,description:base.module_l10n_cl
@ -277,7 +303,7 @@ msgstr ""
#. module: base
#: model:ir.module.module,shortdesc:base.module_sale
msgid "Sales Management"
msgstr ""
msgstr "Manajemen Penjualan"
#. module: base
#: help:res.partner,user_id:0
@ -285,6 +311,7 @@ msgid ""
"The internal user that is in charge of communicating with this contact if "
"any."
msgstr ""
"Pengguna/user yang ditugaskan berhubungan dengan kontak ini, jika ada."
#. module: base
#: view:res.partner:0
@ -299,7 +326,7 @@ msgstr "Jumlah Modul"
#. module: base
#: help:multi_company.default,company_dest_id:0
msgid "Company to store the current record"
msgstr ""
msgstr "Perusahaan untuk menyimpan data ini"
#. module: base
#: field:res.partner.bank.type.field,size:0
@ -312,6 +339,8 @@ msgid ""
"Database ID of record to open in form view, when ``view_mode`` is set to "
"'form' only"
msgstr ""
"Database ID dari data yang akan dibuka dalam tampilan formulir (form view), "
"dimana \"view_mode\" diberi nilai \"form\"."
#. module: base
#: help:ir.values,key2:0
@ -327,7 +356,7 @@ msgstr ""
#. module: base
#: sql_constraint:res.lang:0
msgid "The name of the language must be unique !"
msgstr ""
msgstr "Nama bahasa harus unik"
#. module: base
#: selection:res.request,state:0
@ -355,7 +384,7 @@ msgstr ""
#. module: base
#: model:ir.module.category,name:base.module_category_customer_relationship_management
msgid "Customer Relationship Management"
msgstr ""
msgstr "Manajemen Hubungan Pelanggan"
#. module: base
#: model:ir.module.module,description:base.module_delivery
@ -377,40 +406,42 @@ msgid ""
"There is already a shared filter set as default for %(model)s, delete or "
"change it before setting a new default"
msgstr ""
"Sudah ada filter yang telah ditetapkan untuk %(model)s, silakan hapus atau "
"rubah dulu sebelum menetapkan nilai baru."
#. module: base
#: code:addons/orm.py:2649
#, python-format
msgid "Invalid group_by"
msgstr ""
msgstr "group_by salah"
#. module: base
#: field:ir.module.category,child_ids:0
msgid "Child Applications"
msgstr ""
msgstr "Aplikasi Anak"
#. module: base
#: field:res.partner,credit_limit:0
msgid "Credit Limit"
msgstr ""
msgstr "Batas Kredit"
#. module: base
#: field:ir.model.constraint,date_update:0
#: field:ir.model.data,date_update:0
#: field:ir.model.relation,date_update:0
msgid "Update Date"
msgstr "Tanggal Pembaharuan"
msgstr "Pembaharuan Tanggal"
#. module: base
#: model:ir.module.module,shortdesc:base.module_base_action_rule
msgid "Automated Action Rules"
msgstr ""
msgstr "Aturan Aksi Otomatis"
#. module: base
#: view:ir.attachment:0
#: field:ir.attachment,create_uid:0
msgid "Owner"
msgstr ""
msgstr "Pemilik"
#. module: base
#: view:ir.actions.act_window:0
@ -420,7 +451,7 @@ msgstr "Sumber Obyek"
#. module: base
#: model:res.partner.bank.type,format_layout:base.bank_normal
msgid "%(bank_name)s: %(acc_number)s"
msgstr ""
msgstr "%(bank_name)s: %(acc_number)s"
#. module: base
#: view:ir.actions.todo:0
@ -445,6 +476,8 @@ msgid ""
"Invalid date/time format directive specified. Please refer to the list of "
"allowed directives, displayed when you edit a language."
msgstr ""
"Penetapan format data/time salah. Silakan contoh yang ditampilkan ketika "
"mengedit bahasa."
#. module: base
#: code:addons/orm.py:4153
@ -452,7 +485,7 @@ msgstr ""
msgid ""
"One of the records you are trying to modify has already been deleted "
"(Document type: %s)."
msgstr ""
msgstr "Data yang akan Anda modifikasi sudah dihapus (Jenis Dokumen: %s)."
#. module: base
#: help:ir.actions.act_window,views:0
@ -466,12 +499,12 @@ msgstr ""
#. module: base
#: field:ir.model.relation,name:0
msgid "Relation Name"
msgstr ""
msgstr "Nama Relasi"
#. module: base
#: view:ir.rule:0
msgid "Create Access Right"
msgstr ""
msgstr "Membuat Hak Akses"
#. module: base
#: model:res.country,name:base.tv
@ -481,7 +514,7 @@ msgstr "Tuvalu"
#. module: base
#: field:ir.actions.configuration.wizard,note:0
msgid "Next Wizard"
msgstr ""
msgstr "Wizard berikutnya"
#. module: base
#: field:res.lang,date_format:0
@ -491,7 +524,7 @@ msgstr "Format Tanggal"
#. module: base
#: model:ir.module.module,shortdesc:base.module_base_report_designer
msgid "OpenOffice Report Designer"
msgstr ""
msgstr "Untuk mendesign Laporan dengan OpenOffice"
#. module: base
#: model:res.country,name:base.an

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:46+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:48+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:46+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:48+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:46+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:48+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:45+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:46+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:47+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:48+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:47+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:48+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:47+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:49+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:47+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:49+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:47+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:49+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:48+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:49+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:48+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:49+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:44+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:45+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:52+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:53+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:48+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:49+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing
@ -2052,7 +2052,7 @@ msgstr "Turkmenistan"
#. module: base
#: view:res.lang:0
msgid "7. %H:%M:%S ==> 18:25:20"
msgstr ""
msgstr "7. %H:%M:%S ==> 18:25:20"
#. module: base
#: view:res.partner:0
@ -2579,7 +2579,7 @@ msgstr "Dodatkowe"
#. module: base
#: model:res.country,name:base.st
msgid "Saint Tome (Sao Tome) and Principe"
msgstr ""
msgstr "Wyspy Świętego Tomasza i Książęca"
#. module: base
#: selection:res.partner,type:0
@ -4261,7 +4261,7 @@ msgstr "Wymagana grupa"
#. module: base
#: view:res.lang:0
msgid "6. %d, %m ==> 05, 12"
msgstr ""
msgstr "6. %d, %m ==> 05, 12"
#. module: base
#: model:ir.module.module,shortdesc:base.module_l10n_it
@ -5146,7 +5146,7 @@ msgstr "Ścieżka XML"
#. module: base
#: model:res.country,name:base.bj
msgid "Benin"
msgstr ""
msgstr "Benin"
#. module: base
#: model:ir.actions.act_window,name:base.action_res_partner_bank_type_form
@ -5340,7 +5340,7 @@ msgstr "Syria"
#. module: base
#: view:res.lang:0
msgid "======================================================"
msgstr ""
msgstr "======================================================"
#. module: base
#: sql_constraint:ir.model:0
@ -5453,6 +5453,7 @@ msgstr "Nazwa posiadacza konta"
#, python-format
msgid "Cannot rename column to %s, because that column already exists!"
msgstr ""
"Nie można zmienić nazwy kolumny na %s, ponieważ ta kolumna już istnieje!"
#. module: base
#: view:ir.attachment:0
@ -5578,7 +5579,7 @@ msgstr "Wenezuela"
#. module: base
#: view:res.lang:0
msgid "9. %j ==> 340"
msgstr ""
msgstr "9. %j ==> 340"
#. module: base
#: model:res.country,name:base.zm
@ -5943,7 +5944,7 @@ msgstr "Pogrupuj wg"
#. module: base
#: view:res.config.installer:0
msgid "title"
msgstr ""
msgstr "tytuł"
#. module: base
#: code:addons/base/ir/ir_fields.py:146
@ -5969,7 +5970,7 @@ msgstr "Tłumaczenie"
#. module: base
#: selection:res.request,state:0
msgid "closed"
msgstr ""
msgstr "zamknięte"
#. module: base
#: selection:base.language.export,state:0
@ -12108,7 +12109,7 @@ msgstr ""
#. module: base
#: selection:ir.translation,type:0
msgid "XSL"
msgstr ""
msgstr "XSL"
#. module: base
#: code:addons/base/ir/ir_model.py:85
@ -15906,10 +15907,6 @@ msgstr ""
#~ msgid "On delete"
#~ msgstr "Przy usuwaniu"
#, python-format
#~ msgid "Not Implemented"
#~ msgstr "Nie zaimplementowane"
#~ msgid "Textile Suppliers"
#~ msgstr "Dostawca tekstyliów"
@ -17034,3 +17031,58 @@ msgstr ""
#~ "======================================\n"
#~ "\n"
#~ "Moduł umożliwia planowanie wsteczne zarządzające twoimi wydarzeniami.\n"
#, python-format
#~ msgid "Not Implemented"
#~ msgstr "Niezaimplementowane"
#~ msgid ""
#~ "OpenERP translations (core, modules, clients) are managed through "
#~ "Launchpad.net, our open source project management facility. We use their "
#~ "online interface to synchronize all translations efforts."
#~ msgstr ""
#~ "Tłumaczenia OpenERP (jądro, moduły, klient) są zarządzane poprzez "
#~ "Launchpad.net. Używamy ich interfejsu online by synchronizować wszelkie "
#~ "tłumaczenia."
#~ msgid ","
#~ msgstr ","
#~ msgid "S. Georgia & S. Sandwich Isls."
#~ msgstr "Georgia Południowa i Sandwich Południowy"
#~ msgid "-"
#~ msgstr "-"
#~ msgid "https://help.launchpad.net/Translations"
#~ msgstr "https://help.launchpad.net/Translations"
#~ msgid "Portugese (BR) / Português (BR)"
#~ msgstr "Portugalski (BR) / Português (BR)"
#~ msgid "Facebook"
#~ msgstr "Facebook"
#~ msgid "Openstuff.net"
#~ msgstr "Openstuff.net"
#~ msgid "Portugese / Português"
#~ msgstr "Portugalski / Português"
#~ msgid "M."
#~ msgstr "M."
#~ msgid "XML ID"
#~ msgstr "XML ID"
#~ msgid "XML Id"
#~ msgstr "XML Id"
#~ msgid "FYROM"
#~ msgstr "FYROM"
#~ msgid "Serial Key"
#~ msgstr "Klucz seryjny"
#~ msgid "Widget Wizard"
#~ msgstr "Kreator widżetów"

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:48+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:50+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

File diff suppressed because it is too large Load Diff

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:48+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:50+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:49+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:50+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:49+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:51+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:49+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:51+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:42+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:44+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:49+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:50+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:53+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:55+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:50+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:51+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:50+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:51+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:50+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:51+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:50+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:52+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing
@ -2465,6 +2465,8 @@ msgstr ""
msgid ""
"No matching record found for %(field_type)s '%(value)s' in field '%%(field)s'"
msgstr ""
"'%%(field)s' Alanında %(field_type)s '%(value)s' için eşleşen kayıt "
"bulunamadı"
#. module: base
#: field:change.password.user,new_passwd:0
@ -15574,6 +15576,8 @@ msgid ""
"Tax Identification Number. Check the box if this contact is subjected to "
"taxes. Used by the some of the legal statements."
msgstr ""
"Vergi Kimlik Numarası. Bu kişi vergiye tabiyse kutuyu işaretleyin. Bazı "
"yasal belgelerde kullanılır."
#. module: base
#: field:res.partner.bank,partner_id:0
@ -15642,6 +15646,8 @@ msgid ""
"Manage relations with prospects and customers using leads, opportunities, "
"requests or issues."
msgstr ""
"Adayları, fırsatları, istekleri ya da sorunları kullanarak potansiyeller ve "
"müşterilerle ilişkileri yönetin."
#. module: base
#: model:ir.module.module,description:base.module_project

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:50+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:52+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:50+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:52+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:50+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:52+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:52+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:54+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:51+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:52+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-06 05:52+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-03 05:54+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -19,20 +19,20 @@
#
##############################################################################
from functools import partial
import logging
import operator
import os
import re
from socket import gethostname
import time
import openerp
from openerp import SUPERUSER_ID
from openerp import tools
from openerp import workflow
from openerp.osv import fields, osv
from openerp.osv.orm import browse_record
import openerp.report.interface
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 _
import openerp.workflow
@ -429,121 +429,177 @@ class server_object_lines(osv.osv):
_name = 'ir.server.object.lines'
_sequence = 'ir_actions_id_seq'
_columns = {
'server_id': fields.many2one('ir.actions.server', 'Object Mapping'),
'col1': fields.many2one('ir.model.fields', 'Destination', required=True),
'server_id': fields.many2one('ir.actions.server', 'Related Server Action'),
'col1': fields.many2one('ir.model.fields', 'Field', required=True),
'value': fields.text('Value', required=True, help="Expression containing a value specification. \n"
"When Formula type is selected, this field may be a Python expression "
" that can use the same values as for the condition field on the server action.\n"
"If Value type is selected, the value will be used directly without evaluation."),
'type': fields.selection([
('value','Value'),
('equation','Formula')
], 'Type', required=True, size=32, change_default=True),
('value', 'Value'),
('equation', 'Python expression')
], 'Evaluation Type', required=True, change_default=True),
}
_defaults = {
'type': 'equation',
'type': 'value',
}
server_object_lines()
##
# Actions that are run on the server side
#
class actions_server(osv.osv):
""" Server actions model. Server action work on a base model and offer various
type of actions that can be executed automatically, for example using base
action rules, of manually, by adding the action in the 'More' contextual
menu.
def _select_signals(self, cr, uid, context=None):
cr.execute("""SELECT distinct w.osv, t.signal FROM wkf w, wkf_activity a, wkf_transition t
WHERE w.id = a.wkf_id AND
(t.act_from = a.id OR t.act_to = a.id) AND
t.signal IS NOT NULL""")
result = cr.fetchall() or []
res = []
for rs in result:
if rs[0] is not None and rs[1] is not None:
line = rs[1], "%s - (%s)" % (rs[1], rs[0])
res.append(line)
return res
Since OpenERP 8.0 a button 'Create Menu Action' button is available on the
action form view. It creates an entry in the More menu of the base model.
This allows to create server actions and run them in mass mode easily through
the interface.
def _select_objects(self, cr, uid, context=None):
model_pool = self.pool.get('ir.model')
ids = model_pool.search(cr, uid, [('name','not ilike','.')])
res = model_pool.read(cr, uid, ids, ['model', 'name'])
return [(r['model'], r['name']) for r in res] + [('','')]
def change_object(self, cr, uid, ids, copy_object, state, context=None):
if state == 'object_copy' and copy_object:
if context is None:
context = {}
model_pool = self.pool.get('ir.model')
model = copy_object.split(',')[0]
mid = model_pool.search(cr, uid, [('model','=',model)])
return {
'value': {'srcmodel_id': mid[0]},
'context': context
}
else:
return {}
The available actions are :
- 'Execute Python Code': a block of python code that will be executed
- 'Trigger a Workflow Signal': send a signal to a workflow
- 'Run a Client Action': choose a client action to launch
- 'Create or Copy a new Record': create a new record with new values, or
copy an existing record in your database
- 'Write on a Record': update the values of a record
- 'Execute several actions': define an action that triggers several other
server actions
"""
_name = 'ir.actions.server'
_table = 'ir_act_server'
_inherit = 'ir.actions.actions'
_sequence = 'ir_actions_id_seq'
_order = 'sequence,name'
def _select_objects(self, cr, uid, context=None):
model_pool = self.pool.get('ir.model')
ids = model_pool.search(cr, uid, [('name', 'not ilike', '.')])
res = model_pool.read(cr, uid, ids, ['model', 'name'])
return [(r['model'], r['name']) for r in res] + [('', '')]
def _get_states(self, cr, uid, context=None):
""" Override me in order to add new states in the server action. Please
note that the added key length should not be higher than already-existing
ones. """
return [('code', 'Execute Python Code'),
('trigger', 'Trigger a Workflow Signal'),
('client_action', 'Run a Client Action'),
('object_create', 'Create or Copy a new Record'),
('object_write', 'Write on a Record'),
('multi', 'Execute several actions')]
def _get_states_wrapper(self, cr, uid, context=None):
return self._get_states(cr, uid, context)
_columns = {
'name': fields.char('Action Name', required=True, size=64, translate=True),
'condition' : fields.char('Condition', size=256, required=True,
help="Condition that is tested before the action is executed, "
"and prevent execution if it is not verified.\n"
"Example: object.list_price > 5000\n"
"It is a Python expression that can use the following values:\n"
" - self: ORM model of the record on which the action is triggered\n"
" - object or obj: browse_record of the record on which the action is triggered\n"
" - pool: ORM model pool (i.e. self.pool)\n"
" - time: Python time module\n"
" - cr: database cursor\n"
" - uid: current user id\n"
" - context: current context"),
'state': fields.selection([
('client_action','Client Action'),
('dummy','Dummy'),
('loop','Iteration'),
('code','Python Code'),
('trigger','Trigger'),
('email','Email'),
('sms','SMS'),
('object_create','Create Object'),
('object_copy','Copy Object'),
('object_write','Write Object'),
('other','Multi Actions'),
], 'Action Type', required=True, size=32, help="Type of the Action that is to be executed"),
'code':fields.text('Python Code', help="Python code to be executed if condition is met.\n"
"It is a Python block that can use the same values as for the condition field"),
'sequence': fields.integer('Sequence', help="Important when you deal with multiple actions, the execution order will be decided based on this, low number is higher priority."),
'model_id': fields.many2one('ir.model', 'Object', required=True, help="Select the object on which the action will work (read, write, create).", ondelete='cascade'),
'action_id': fields.many2one('ir.actions.actions', 'Client Action', help="Select the Action Window, Report, Wizard to be executed."),
'trigger_name': fields.selection(_select_signals, string='Trigger Signal', size=128, help="The workflow signal to trigger"),
'wkf_model_id': fields.many2one('ir.model', 'Target Object', help="The object that should receive the workflow signal (must have an associated workflow)"),
'trigger_obj_id': fields.many2one('ir.model.fields','Relation Field', help="The field on the current object that links to the target object record (must be a many2one, or an integer field with the record ID)"),
'email': fields.char('Email Address', size=512, help="Expression that returns the email address to send to. Can be based on the same values as for the condition field.\n"
"Example: object.invoice_address_id.email, or 'me@example.com'"),
'subject': fields.char('Subject', size=1024, translate=True, help="Email subject, may contain expressions enclosed in double brackets based on the same values as those "
"available in the condition field, e.g. `Hello [[ object.partner_id.name ]]`"),
'message': fields.text('Message', translate=True, help="Email contents, may contain expressions enclosed in double brackets based on the same values as those "
"available in the condition field, e.g. `Dear [[ object.partner_id.name ]]`"),
'mobile': fields.char('Mobile No', size=512, help="Provides fields that be used to fetch the mobile number, e.g. you select the invoice, then `object.invoice_address_id.mobile` is the field which gives the correct mobile number"),
'sms': fields.char('SMS', size=160, translate=True),
'child_ids': fields.many2many('ir.actions.server', 'rel_server_actions', 'server_id', 'action_id', 'Other Actions'),
'condition': fields.char('Condition',
help="Condition verified before executing the server action. If it "
"is not verified, the action will not be executed. The condition is "
"a Python expression, like 'object.list_price > 5000'. A void "
"condition is considered as always True. Help about python expression "
"is given in the help tab."),
'state': fields.selection(_get_states_wrapper, 'Action To Do', required=True,
help="Type of server action. The following values are available:\n"
"- 'Execute Python Code': a block of python code that will be executed\n"
"- 'Trigger a Workflow Signal': send a signal to a workflow\n"
"- 'Run a Client Action': choose a client action to launch\n"
"- 'Create or Copy a new Record': create a new record with new values, or copy an existing record in your database\n"
"- 'Write on a Record': update the values of a record\n"
"- 'Execute several actions': define an action that triggers several other server actions\n"
"- 'Send Email': automatically send an email (available in email_template)"),
'usage': fields.char('Action Usage', size=32),
'type': fields.char('Action Type', size=32, required=True),
'srcmodel_id': fields.many2one('ir.model', 'Model', help="Object in which you want to create / write the object. If it is empty then refer to the Object field."),
'fields_lines': fields.one2many('ir.server.object.lines', 'server_id', 'Field Mappings.'),
'record_id':fields.many2one('ir.model.fields', 'Create Id', help="Provide the field name where the record id is stored after the create operations. If it is empty, you can not track the new record."),
'write_id':fields.char('Write Id', size=256, help="Provide the field name that the record id refers to for the write operation. If it is empty it will refer to the active id of the object."),
'loop_action':fields.many2one('ir.actions.server', 'Loop Action', help="Select the action that will be executed. Loop action will not be avaliable inside loop."),
'expression':fields.char('Loop Expression', size=512, help="Enter the field/expression that will return the list. E.g. select the sale order in Object, and you can have loop on the sales order line. Expression = `object.order_line`."),
'copy_object': fields.reference('Copy Of', selection=_select_objects, size=256),
# Generic
'sequence': fields.integer('Sequence',
help="When dealing with multiple actions, the execution order is "
"based on the sequence. Low number means high priority."),
'model_id': fields.many2one('ir.model', 'Base Model', required=True, ondelete='cascade',
help="Base model on which the server action runs."),
'menu_ir_values_id': fields.many2one('ir.values', 'More Menu entry', readonly=True,
help='More menu entry.'),
# Client Action
'action_id': fields.many2one('ir.actions.actions', 'Client Action',
help="Select the client action that has to be executed."),
# Python code
'code': fields.text('Python Code',
help="Write Python code that the action will execute. Some variables are "
"available for use; help about pyhon expression is given in the help tab."),
# Workflow signal
'use_relational_model': fields.selection([('base', 'Use the base model of the action'),
('relational', 'Use a relation field on the base model')],
string='Target Model', required=True),
'wkf_transition_id': fields.many2one('workflow.transition', string='Signal to Trigger',
help="Select the workflow signal to trigger."),
'wkf_model_id': fields.many2one('ir.model', 'Target Model',
help="The model that will receive the workflow signal. Note that it should have a workflow associated with it."),
'wkf_model_name': fields.related('wkf_model_id', 'model', type='char', string='Target Model Name', store=True, readonly=True),
'wkf_field_id': fields.many2one('ir.model.fields', string='Relation Field',
oldname='trigger_obj_id',
help="The field on the current object that links to the target object record (must be a many2one, or an integer field with the record ID)"),
# Multi
'child_ids': fields.many2many('ir.actions.server', 'rel_server_actions',
'server_id', 'action_id',
string='Child Actions',
help='Child server actions that will be executed. Note that the last return returned action value will be used as global return value.'),
# Create/Copy/Write
'use_create': fields.selection([('new', 'Create a new record in the Base Model'),
('new_other', 'Create a new record in another model'),
('copy_current', 'Copy the current record'),
('copy_other', 'Choose and copy a record in the database')],
string="Creation Policy", required=True,
help=""),
'crud_model_id': fields.many2one('ir.model', 'Target Model',
oldname='srcmodel_id',
help="Model for record creation / update. Set this field only to specify a different model than the base model."),
'crud_model_name': fields.related('crud_model_id', 'model', type='char',
string='Create/Write Target Model Name',
store=True, readonly=True),
'ref_object': fields.reference('Reference record', selection=_select_objects, size=128,
oldname='copy_object'),
'link_new_record': fields.boolean('Attach the new record',
help="Check this if you want to link the newly-created record "
"to the current record on which the server action runs."),
'link_field_id': fields.many2one('ir.model.fields', 'Link using field',
oldname='record_id',
help="Provide the field where the record id is stored after the operations."),
'use_write': fields.selection([('current', 'Update the current record'),
('expression', 'Update a record linked to the current record using python'),
('other', 'Choose and Update a record in the database')],
string='Update Policy', required=True,
help=""),
'write_expression': fields.char('Expression',
oldname='write_id',
help="Provide an expression that, applied on the current record, gives the field to update."),
'fields_lines': fields.one2many('ir.server.object.lines', 'server_id',
string='Value Mapping',
help=""),
# Fake fields used to implement the placeholder assistant
'model_object_field': fields.many2one('ir.model.fields', string="Field",
help="Select target field from the related document model.\n"
"If it is a relationship field you will be able to select "
"a target field at the destination of the relationship."),
'sub_object': fields.many2one('ir.model', 'Sub-model', readonly=True,
help="When a relationship field is selected as first field, "
"this field shows the document model the relationship goes to."),
'sub_model_object_field': fields.many2one('ir.model.fields', 'Sub-field',
help="When a relationship field is selected as first field, "
"this field lets you select the target field within the "
"destination document model (sub-model)."),
'copyvalue': fields.char('Placeholder Expression', help="Final placeholder expression, to be copy-pasted in the desired template field."),
# Fake fields used to implement the ID finding assistant
'id_object': fields.reference('Record', selection=_select_objects, size=128),
'id_value': fields.char('Record ID'),
}
_defaults = {
'state': 'dummy',
'state': 'code',
'condition': 'True',
'type': 'ir.actions.server',
'sequence': 5,
@ -551,246 +607,427 @@ class actions_server(osv.osv):
# - self: ORM model of the record on which the action is triggered
# - object: browse_record of the record on which the action is triggered if there is one, otherwise None
# - pool: ORM model pool (i.e. self.pool)
# - time: Python time module
# - cr: database cursor
# - uid: current user id
# - context: current context
# If you plan to return an action, assign: action = {...}
""",
# - time: Python time module
# If you plan to return an action, assign: action = {...}""",
'use_relational_model': 'base',
'use_create': 'new',
'use_write': 'current',
}
def get_email(self, cr, uid, action, context):
def _check_expression(self, cr, uid, expression, model_id, context):
""" Check python expression (condition, write_expression). Each step of
the path must be a valid many2one field, or an integer field for the last
step.
:param str expression: a python expression, beginning by 'obj' or 'object'
:param int model_id: the base model of the server action
:returns tuple: (is_valid, target_model_name, error_msg)
"""
if not model_id:
return (False, None, 'Your expression cannot be validated because the Base Model is not set.')
# fetch current model
current_model_name = self.pool.get('ir.model').browse(cr, uid, model_id, context).model
# transform expression into a path that should look like 'object.many2onefield.many2onefield'
path = expression.split('.')
initial = path.pop(0)
if initial not in ['obj', 'object']:
return (False, None, 'Your expression should begin with obj or object.\nAn expression builder is available in the help tab.')
# analyze path
while path:
step = path.pop(0)
column_info = self.pool[current_model_name]._all_columns.get(step)
if not column_info:
return (False, None, 'Part of the expression (%s) is not recognized as a column in the model %s.' % (step, current_model_name))
column_type = column_info.column._type
if column_type not in ['many2one', 'int']:
return (False, None, 'Part of the expression (%s) is not a valid column type (is %s, should be a many2one or an int)' % (step, column_type))
if column_type == 'int' and path:
return (False, None, 'Part of the expression (%s) is an integer field that is only allowed at the end of an expression' % (step))
if column_type == 'many2one':
current_model_name = column_info.column._obj
return (True, current_model_name, None)
def _check_write_expression(self, cr, uid, ids, context=None):
for record in self.browse(cr, uid, ids, context=context):
if record.write_expression and record.model_id:
correct, model_name, message = self._check_expression(cr, uid, record.write_expression, record.model_id.id, context=context)
if not correct:
_logger.warning('Invalid expression: %s' % message)
return False
return True
_constraints = [
(_check_write_expression,
'Incorrect Write Record Expression',
['write_expression']),
(partial(osv.Model._check_m2m_recursion, field_name='child_ids'),
'Recursion found in child server actions',
['child_ids']),
]
def on_change_model_id(self, cr, uid, ids, model_id, wkf_model_id, crud_model_id, context=None):
""" When changing the action base model, reset workflow and crud config
to ease value coherence. """
values = {
'use_create': 'new',
'use_write': 'current',
'use_relational_model': 'base',
'wkf_model_id': model_id,
'wkf_field_id': False,
'crud_model_id': model_id,
}
return {'value': values}
def on_change_wkf_wonfig(self, cr, uid, ids, use_relational_model, wkf_field_id, wkf_model_id, model_id, context=None):
""" Update workflow type configuration
- update the workflow model (for base (model_id) /relational (field.relation))
- update wkf_transition_id to False if workflow model changes, to force
the user to choose a new one
"""
values = {}
if use_relational_model == 'relational' and wkf_field_id:
field = self.pool['ir.model.fields'].browse(cr, uid, wkf_field_id, context=context)
new_wkf_model_id = self.pool.get('ir.model').search(cr, uid, [('model', '=', field.relation)], context=context)[0]
values['wkf_model_id'] = new_wkf_model_id
else:
values['wkf_model_id'] = model_id
return {'value': values}
def on_change_wkf_model_id(self, cr, uid, ids, wkf_model_id, context=None):
""" When changing the workflow model, update its stored name also """
wkf_model_name = False
if wkf_model_id:
wkf_model_name = self.pool.get('ir.model').browse(cr, uid, wkf_model_id, context).model
values = {'wkf_transition_id': False, 'wkf_model_name': wkf_model_name}
return {'value': values}
def on_change_crud_config(self, cr, uid, ids, state, use_create, use_write, ref_object, crud_model_id, model_id, context=None):
""" Wrapper on CRUD-type (create or write) on_change """
if state == 'object_create':
return self.on_change_create_config(cr, uid, ids, use_create, ref_object, crud_model_id, model_id, context=context)
elif state == 'object_write':
return self.on_change_write_config(cr, uid, ids, use_write, ref_object, crud_model_id, model_id, context=context)
else:
return {}
def on_change_create_config(self, cr, uid, ids, use_create, ref_object, crud_model_id, model_id, context=None):
""" When changing the object_create type configuration:
- `new` and `copy_current`: crud_model_id is the same as base model
- `new_other`: user choose crud_model_id
- `copy_other`: disassemble the reference object to have its model
- if the target model has changed, then reset the link field that is
probably not correct anymore
"""
values = {}
if use_create == 'new':
values['crud_model_id'] = model_id
elif use_create == 'new_other':
pass
elif use_create == 'copy_current':
values['crud_model_id'] = model_id
elif use_create == 'copy_other' and ref_object:
ref_model, ref_id = ref_object.split(',')
ref_model_id = self.pool['ir.model'].search(cr, uid, [('model', '=', ref_model)], context=context)[0]
values['crud_model_id'] = ref_model_id
if values.get('crud_model_id') != crud_model_id:
values['link_field_id'] = False
return {'value': values}
def on_change_write_config(self, cr, uid, ids, use_write, ref_object, crud_model_id, model_id, context=None):
""" When changing the object_write type configuration:
- `current`: crud_model_id is the same as base model
- `other`: disassemble the reference object to have its model
- `expression`: has its own on_change, nothing special here
"""
values = {}
if use_write == 'current':
values['crud_model_id'] = model_id
elif use_write == 'other' and ref_object:
ref_model, ref_id = ref_object.split(',')
ref_model_id = self.pool['ir.model'].search(cr, uid, [('model', '=', ref_model)], context=context)[0]
values['crud_model_id'] = ref_model_id
elif use_write == 'expression':
pass
if values.get('crud_model_id') != crud_model_id:
values['link_field_id'] = False
return {'value': values}
def on_change_write_expression(self, cr, uid, ids, write_expression, model_id, context=None):
""" Check the write_expression and update crud_model_id accordingly """
values = {}
valid, model_name, message = self._check_expression(cr, uid, write_expression, model_id, context=context)
if valid:
ref_model_id = self.pool['ir.model'].search(cr, uid, [('model', '=', model_name)], context=context)[0]
values['crud_model_id'] = ref_model_id
return {'value': values}
if not message:
message = 'Invalid expression'
return {
'warning': {
'title': 'Incorrect expression',
'message': message,
}
}
def on_change_crud_model_id(self, cr, uid, ids, crud_model_id, context=None):
""" When changing the CRUD model, update its stored name also """
crud_model_name = False
if crud_model_id:
crud_model_name = self.pool.get('ir.model').browse(cr, uid, crud_model_id, context).model
values = {'link_field_id': False, 'crud_model_name': crud_model_name}
return {'value': values}
def _build_expression(self, field_name, sub_field_name):
""" Returns a placeholder expression for use in a template field,
based on the values provided in the placeholder assistant.
:param field_name: main field name
:param sub_field_name: sub field name (M2O)
:return: final placeholder expression
"""
expression = ''
if field_name:
expression = "object." + field_name
if sub_field_name:
expression += "." + sub_field_name
return expression
def onchange_sub_model_object_value_field(self, cr, uid, ids, model_object_field, sub_model_object_field=False, context=None):
result = {
'sub_object': False,
'copyvalue': False,
'sub_model_object_field': False,
}
if model_object_field:
fields_obj = self.pool.get('ir.model.fields')
field_value = fields_obj.browse(cr, uid, model_object_field, context)
if field_value.ttype in ['many2one', 'one2many', 'many2many']:
res_ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', field_value.relation)], context=context)
sub_field_value = False
if sub_model_object_field:
sub_field_value = fields_obj.browse(cr, uid, sub_model_object_field, context)
if res_ids:
result.update({
'sub_object': res_ids[0],
'copyvalue': self._build_expression(field_value.name, sub_field_value and sub_field_value.name or False),
'sub_model_object_field': sub_model_object_field or False,
})
else:
result.update({
'copyvalue': self._build_expression(field_value.name, False),
})
return {'value': result}
def onchange_id_object(self, cr, uid, ids, id_object, context=None):
if id_object:
ref_model, ref_id = id_object.split(',')
return {'value': {'id_value': ref_id}}
return {'value': {'id_value': False}}
def create_action(self, cr, uid, ids, context=None):
""" Create a contextual action for each of the server actions. """
for action in self.browse(cr, uid, ids, context=context):
ir_values_id = self.pool.get('ir.values').create(cr, SUPERUSER_ID, {
'name': _('Run %s') % action.name,
'model': action.model_id.model,
'key2': 'client_action_multi',
'value': "ir.actions.server,%s" % action.id,
}, context)
action.write({
'menu_ir_values_id': ir_values_id,
})
return True
def unlink_action(self, cr, uid, ids, context=None):
""" Remove the contextual actions created for the server actions. """
for action in self.browse(cr, uid, ids, context=context):
if action.menu_ir_values_id:
try:
self.pool.get('ir.values').unlink(cr, SUPERUSER_ID, action.menu_ir_values_id.id, context)
except Exception:
raise osv.except_osv(_('Warning'), _('Deletion of the action record failed.'))
return True
def run_action_client_action(self, cr, uid, action, eval_context=None, context=None):
if not action.action_id:
raise osv.except_osv(_('Error'), _("Please specify an action to launch!"))
return self.pool[action.action_id.type].read(cr, uid, action.action_id.id, context=context)
def run_action_code(self, cr, uid, action, eval_context=None, context=None):
eval(action.code.strip(), eval_context, mode="exec", nocopy=True) # nocopy allows to return 'action'
if 'action' in eval_context:
return eval_context['action']
def run_action_trigger(self, cr, uid, action, eval_context=None, context=None):
""" Trigger a workflow signal, depending on the use_relational_model:
- `base`: base_model_pool.signal_<TRIGGER_NAME>(cr, uid, context.get('active_id'))
- `relational`: find the related model and object, using the relational
field, then target_model_pool.signal_<TRIGGER_NAME>(cr, uid, target_id)
"""
obj_pool = self.pool[action.model_id.model]
id = context.get('active_id')
obj = obj_pool.browse(cr, uid, id)
if action.use_relational_model == 'base':
target_id = context.get('active_id')
target_pool = obj_pool
else:
value = getattr(obj_pool.browse(cr, uid, context.get('active_id'), context=context), action.wkf_field_id.name)
if action.wkf_field_id.ttype == 'many2one':
target_id = value.id
else:
target_id = value
target_pool = self.pool[action.wkf_model_id.model]
fields = None
trigger_name = action.wkf_transition_id.signal
if '/' in action.email.complete_name:
fields = action.email.complete_name.split('/')
elif '.' in action.email.complete_name:
fields = action.email.complete_name.split('.')
workflow.trg_validate(uid, target_pool._name, target_id, trigger_name, cr)
for field in fields:
try:
obj = getattr(obj, field)
except Exception:
_logger.exception('Failed to parse: %s', field)
def run_action_multi(self, cr, uid, action, eval_context=None, context=None):
res = []
for act in action.child_ids:
result = self.run(cr, uid, [act.id], context)
if result:
res.append(result)
return res and res[0] or False
return obj
def run_action_object_write(self, cr, uid, action, eval_context=None, context=None):
""" Write server action.
def get_mobile(self, cr, uid, action, context):
obj_pool = self.pool[action.model_id.model]
id = context.get('active_id')
obj = obj_pool.browse(cr, uid, id)
- 1. evaluate the value mapping
- 2. depending on the write configuration:
fields = None
- `current`: id = active_id
- `other`: id = from reference object
- `expression`: id = from expression evaluation
"""
res = {}
for exp in action.fields_lines:
if exp.type == 'equation':
expr = eval(exp.value, eval_context)
else:
expr = exp.value
res[exp.col1.name] = expr
if '/' in action.mobile.complete_name:
fields = action.mobile.complete_name.split('/')
elif '.' in action.mobile.complete_name:
fields = action.mobile.complete_name.split('.')
if action.use_write == 'current':
model = action.model_id.model
ref_id = context.get('active_id')
elif action.use_write == 'other':
model = action.crud_model_id.model
ref_id = action.ref_object.id
elif action.use_write == 'expression':
model = action.crud_model_id.model
ref = eval(action.write_expression, eval_context)
if isinstance(ref, browse_record):
ref_id = getattr(ref, 'id')
else:
ref_id = int(ref)
for field in fields:
try:
obj = getattr(obj, field)
except Exception:
_logger.exception('Failed to parse: %s', field)
obj_pool = self.pool[model]
obj_pool.write(cr, uid, [ref_id], res, context=context)
return obj
def run_action_object_create(self, cr, uid, action, eval_context=None, context=None):
""" Create and Copy server action.
def merge_message(self, cr, uid, keystr, action, context=None):
if context is None:
context = {}
- 1. evaluate the value mapping
- 2. depending on the write configuration:
def merge(match):
obj_pool = self.pool[action.model_id.model]
id = context.get('active_id')
obj = obj_pool.browse(cr, uid, id)
exp = str(match.group()[2:-2]).strip()
result = eval(exp,
{
'object': obj,
'context': dict(context), # copy context to prevent side-effects of eval
'time': time,
})
if result in (None, False):
return str("--------")
return tools.ustr(result)
- `new`: new record in the base model
- `copy_current`: copy the current record (id = active_id) + gives custom values
- `new_other`: new record in target model
- `copy_other`: copy the current record (id from reference object)
+ gives custom values
"""
res = {}
for exp in action.fields_lines:
if exp.type == 'equation':
expr = eval(exp.value, eval_context)
else:
expr = exp.value
res[exp.col1.name] = expr
com = re.compile('(\[\[.+?\]\])')
message = com.sub(merge, keystr)
if action.use_create in ['new', 'copy_current']:
model = action.model_id.model
elif action.use_create in ['new_other', 'copy_other']:
model = action.crud_model_id.model
return message
obj_pool = self.pool[model]
if action.use_create == 'copy_current':
ref_id = context.get('active_id')
res_id = obj_pool.copy(cr, uid, ref_id, res, context=context)
elif action.use_create == 'copy_other':
ref_id = action.ref_object.id
res_id = obj_pool.copy(cr, uid, ref_id, res, context=context)
else:
res_id = obj_pool.create(cr, uid, res, context=context)
# Context should contains:
# ids : original ids
# id : current id of the object
# OUT:
# False : Finished correctly
# ACTION_ID : Action to launch
if action.link_new_record and action.link_field_id:
self.pool[action.model_id.model].write(cr, uid, [context.get('active_id')], {action.link_field_id.name: res_id})
# FIXME: refactor all the eval() calls in run()!
def run(self, cr, uid, ids, context=None):
""" Run the server action. For each server action, the condition is
checked. Note that A void (aka False) condition is considered as always
valid. If it is verified, the run_action_<STATE> method is called. This
allows easy inheritance of the server actions.
:param dict context: context should contain following keys
- active_id: id of the current object (single mode)
- active_model: current model that should equal the action's model
The following keys are optional:
- active_ids: ids of the current records (mass mode). If active_ids
and active_id are present, active_ids is given precedence.
:return: an action_id to be executed, or False is finished correctly without
return action
"""
if context is None:
context = {}
res = False
user = self.pool.get('res.users').browse(cr, uid, uid)
active_ids = context.get('active_ids', [context.get('active_id', None)])
for action in self.browse(cr, uid, ids, context):
obj = None
obj_pool = self.pool[action.model_id.model]
if context.get('active_model') == action.model_id.model and context.get('active_id'):
obj = obj_pool.browse(cr, uid, context['active_id'], context=context)
cxt = {
'self': obj_pool,
'object': obj,
'obj': obj,
'pool': self.pool,
'time': time,
'cr': cr,
'context': dict(context), # copy context to prevent side-effects of eval
'uid': uid,
'user': user
}
expr = eval(str(action.condition), cxt)
if not expr:
continue
for active_id in active_ids:
if context.get('active_model') == action.model_id.model and active_id:
obj = obj_pool.browse(cr, uid, context['active_id'], context=context)
# run context dedicated to a particular active_id
run_context = dict(context, active_ids=[active_id], active_id=active_id)
# evaluation context for python strings to evaluate
eval_context = {
'self': obj_pool,
'object': obj,
'obj': obj,
'pool': self.pool,
'time': time,
'cr': cr,
'context': dict(run_context), # copy context to prevent side-effects of eval
'uid': uid,
'user': user
}
if action.state=='client_action':
if not action.action_id:
raise osv.except_osv(_('Error'), _("Please specify an action to launch!"))
return self.pool[action.action_id.type].read(cr, uid, action.action_id.id, context=context)
if action.state=='code':
eval(action.code.strip(), cxt, mode="exec", nocopy=True) # nocopy allows to return 'action'
if 'action' in cxt:
return cxt['action']
if action.state == 'email':
email_from = config['email_from']
if not email_from:
_logger.debug('--email-from command line option is not specified, using a fallback value instead.')
if user.email:
email_from = user.email
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.')
# evaluate the condition, with the specific case that a void (aka False) condition is considered as True
condition = action.condition
if action.condition is False:
condition = True
expr = eval(str(condition), eval_context)
if not expr:
continue
# call the method related to the action: run_action_<STATE>
if hasattr(self, 'run_action_%s' % action.state):
res = getattr(self, 'run_action_%s' % action.state)(cr, uid, action, eval_context=eval_context, context=run_context)
return res
# 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, addresses, subject, body)
res_email = ir_mail_server.send_email(cr, uid, msg)
if res_email:
_logger.info('Email successfully sent to: %s', addresses)
else:
_logger.warning('Failed to send email to: %s', addresses)
if action.state == 'trigger':
model = action.wkf_model_id.model
m2o_field_name = action.trigger_obj_id.name
target_id = obj_pool.read(cr, uid, context.get('active_id'), [m2o_field_name])[m2o_field_name]
target_id = target_id[0] if isinstance(target_id,tuple) else target_id
openerp.workflow.trg_validate(uid, model, int(target_id), action.trigger_name, cr)
if action.state == 'sms':
#TODO: set the user and password from the system
# for the sms gateway user / password
# USE smsclient module from extra-addons
_logger.warning('SMS Facility has not been implemented yet. Use smsclient module!')
if action.state == 'other':
res = []
for act in action.child_ids:
context['active_id'] = context['active_ids'][0]
result = self.run(cr, uid, [act.id], context)
if result:
res.append(result)
return res
if action.state == 'loop':
expr = eval(str(action.expression), cxt)
context['object'] = obj
for i in expr:
context['active_id'] = i.id
self.run(cr, uid, [action.loop_action.id], context)
if action.state == 'object_write':
res = {}
for exp in action.fields_lines:
euq = exp.value
if exp.type == 'equation':
expr = eval(euq, cxt)
else:
expr = exp.value
res[exp.col1.name] = expr
if not action.write_id:
if not action.srcmodel_id:
obj_pool = self.pool[action.model_id.model]
obj_pool.write(cr, uid, [context.get('active_id')], res)
else:
write_id = context.get('active_id')
obj_pool = self.pool[action.srcmodel_id.model]
obj_pool.write(cr, uid, [write_id], res)
elif action.write_id:
obj_pool = self.pool[action.srcmodel_id.model]
rec = self.pool[action.model_id.model].browse(cr, uid, context.get('active_id'))
id = eval(action.write_id, {'object': rec})
try:
id = int(id)
except:
raise osv.except_osv(_('Error'), _("Problem in configuration `Record Id` in Server Action!"))
if type(id) != type(1):
raise osv.except_osv(_('Error'), _("Problem in configuration `Record Id` in Server Action!"))
write_id = id
obj_pool.write(cr, uid, [write_id], res)
if action.state == 'object_create':
res = {}
for exp in action.fields_lines:
euq = exp.value
if exp.type == 'equation':
expr = eval(euq, cxt)
else:
expr = exp.value
res[exp.col1.name] = expr
obj_pool = self.pool[action.srcmodel_id.model]
res_id = obj_pool.create(cr, uid, res)
if action.record_id:
self.pool[action.model_id.model].write(cr, uid, [context.get('active_id')], {action.record_id.name:res_id})
if action.state == 'object_copy':
res = {}
for exp in action.fields_lines:
euq = exp.value
if exp.type == 'equation':
expr = eval(euq, cxt)
else:
expr = exp.value
res[exp.col1.name] = expr
model = action.copy_object.split(',')[0]
cid = action.copy_object.split(',')[1]
obj_pool = self.pool[model]
obj_pool.copy(cr, uid, int(cid), res)
return False
actions_server()
class act_window_close(osv.osv):
_name = 'ir.actions.act_window_close'

View File

@ -309,86 +309,208 @@
<field name="model">ir.actions.server</field>
<field name="arch" type="xml">
<form string="Server Action" version="7.0">
<group>
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1><field name="name"/></h1>
</div>
<div class="oe_right oe_button_box" name="buttons">
<field name="menu_ir_values_id" invisible="1"/>
<button name="create_action" string="Add in the 'More' menu" type="object"
attrs="{'invisible':[('menu_ir_values_id','!=',False)]}"
help="Display an option on related documents to run this sever action"/>
<button name="unlink_action" string="Remove from the 'More' menu" type="object"
attrs="{'invisible':[('menu_ir_values_id','=',False)]}"
help="Remove the contextual action related to this server action"/>
</div>
<group>
<field name="name"/>
<field name="model_id"/>
<field name="state"/>
<group>
<field name="type" invisible="1"/>
<field name="model_id"
on_change="on_change_model_id(model_id, wkf_model_id, crud_model_id)"/>
<field name="state"/>
</group>
<group>
<field name="condition"/>
<field name="sequence"/>
</group>
</group>
<group>
<field name="condition"/>
<field name="sequence"/>
</group>
</group>
<notebook colspan="4">
<page string="Python Code" attrs="{'invisible':[('state','!=','code')]}">
<field name="code"/>
</page>
<page string="Trigger" attrs="{'invisible':[('state','!=','trigger')]}">
<group string="Trigger Configuration" col="4">
<field name="wkf_model_id" attrs="{'required':[('state','=','trigger')]}"/>
<field name="trigger_obj_id" context="{'key':''}"
domain="[('model_id','=',model_id),('ttype','in',['many2one','int'])]"
attrs="{'required':[('state','=','trigger')]}"/>
<field name="trigger_name" attrs="{'required':[('state','=','trigger')]}"/>
</group>
</page>
<page string="Action to Launch" attrs="{'invisible':[('state','!=','client_action')]}">
<group>
<field name="action_id" attrs="{'required':[('state','=','client_action')]}"/>
</group>
</page>
<page string="Email Configuration" attrs="{'invisible':[('state','!=','email')]}">
<group>
<field name="email" domain="[('model_id','=',model_id)]" attrs="{'required':[('state','=','email')]}"/>
<field name="subject" attrs="{'required':[('state','=','email')]}"/>
<field name="message" attrs="{'required':[('state','=','email')]}"/>
<newline/>
<label colspan="2" string="Access all the fields related to the current object using expressions, i.e. object.partner_id.name " align="0.0"/>
</group>
</page>
<page string="SMS Configuration" attrs="{'invisible':[('state','!=','sms')]}">
<group>
<field name="mobile" domain="[('model_id','=',model_id)]" attrs="{'required':[('state','=','sms')]}"/>
<field name="sms" attrs="{'required':[('state','=','sms')]}"/>
</group>
<label string="Access all the fields related to the current object using expressions, i.e. object.partner_id.name " align="0.0"/>
</page>
<page string="Create / Write / Copy" attrs="{'invisible':[('state','!=','object_create'), ('state','!=','object_write'), ('state','!=','object_copy')]}">
<group col="4" string="Fields Mapping">
<field name="srcmodel_id" attrs="{'required':[('state','!=','dummy'), ('state','!=','sms'), ('state','!=','code'), ('state','!=','loop'), ('state','!=','trigger'), ('state','!=','object_copy'), ('state','!=','client_action'), ('state','!=','email'), ('state','!=','sms'), ('state','!=','other')]}"/>
<field name="copy_object" on_change="change_object(copy_object, state)" attrs="{'required':[('state','!=','dummy'), ('state','!=','sms'), ('state','!=','code'), ('state','!=','loop'), ('state','!=','trigger'), ('state','!=','object_write'), ('state','!=','object_create'), ('state','!=','client_action'), ('state','!=','email'), ('state','!=','sms'), ('state','!=','other')]}"/>
<field name="fields_lines" nolabel="1" colspan="2">
<tree string="Field Mappings" editable="top">
<field name="col1" domain="[('model_id','=',parent.srcmodel_id or parent.model_id)]"/>
<field name="type"/>
<field name="value" colspan="4"/>
</tree>
<form string="Field Mapping" version="7.0">
<group col="4">
<field name="col1" domain="[('model_id','=',parent.srcmodel_id or parent.model_id)]"/>
<field name="type"/>
<field name="value" colspan="4"/>
<notebook colspan="4">
<page string="Python Code" name='code' autofocus="autofocus"
attrs="{'invisible': [('state', '!=', 'code')]}">
<field name="code" placeholder="Enter Python code here. Help about Python expression is available in the help tab of this document."/>
</page>
<page string="Worflow Signal" autofocus="autofocus"
attrs="{'invisible': [('state', '!=', 'trigger')]}">
<p attrs="{'invisible': [('model_id', '!=', False)]}">
Please set the Base Model before setting the action details.
</p>
<group attrs="{'invisible': [('model_id', '=', False)]}">
<field name="use_relational_model" widget="radio"
on_change="on_change_wkf_wonfig(use_relational_model, wkf_field_id, wkf_model_id, model_id)"
attrs="{'readonly': [('model_id', '=', False)]}"/>
<field name="wkf_field_id" context="{'key': ''}"
on_change="on_change_wkf_wonfig(use_relational_model, wkf_field_id, wkf_model_id, model_id)"
attrs="{'required': [('state', '=', 'trigger'), ('use_relational_model', '=', 'relational')],
'invisible': [('use_relational_model', '=', 'base')]}"
domain="[('model_id', '=', model_id), ('ttype', 'in', ['many2one'])]"/>
<field name="wkf_model_id" invisible="1"
on_change="on_change_wkf_model_id(wkf_model_id)"/>
<field name="wkf_model_name" invisible="1"/>
<field name="wkf_transition_id" attrs="{'required': [('state', '=', 'trigger')]}"
domain="[('wkf_id.osv', '=', wkf_model_name)]"/>
</group>
</page>
<page string="Client" autofocus="autofocus"
attrs="{'invisible': [('state', '!=', 'client_action')]}">
<group>
<field name="action_id" attrs="{'required':[('state', '=', 'client_action')]}"/>
</group>
</page>
<page string="Create / Write / Copy" autofocus="autofocus"
attrs="{'invisible':[('state', 'not in', ['object_create', 'object_write'])]}">
<p attrs="{'invisible': [('model_id', '!=', False)]}">
Please set the Base Model before setting the action details.
</p>
<group attrs="{'invisible': [('model_id', '=', False)]}">
<field name="use_create" widget="radio"
on_change="on_change_crud_config(state, use_create, use_write, ref_object, crud_model_id, model_id)"
attrs="{'invisible': [('state', '!=', 'object_create')]}"/>
<field name="use_write" widget="radio"
on_change="on_change_crud_config(state, use_create, use_write, ref_object, crud_model_id, model_id)"
attrs="{'invisible': [('state', '!=', 'object_write')]}"/>
<label for="ref_object" string=" "
attrs="{'invisible': ['&amp;',
'|', ('state', '!=', 'object_write'), ('use_write', '!=', 'other'),
'|', ('state', '!=', 'object_create'), ('use_create', '!=', 'copy_other')]}"/>
<div style="margin-left: 24px;"
attrs="{'invisible': ['&amp;',
'|', ('state', '!=', 'object_write'), ('use_write', '!=', 'other'),
'|', ('state', '!=', 'object_create'), ('use_create', '!=', 'copy_other')]}">
<field name="ref_object" nolabel="1"
on_change="on_change_crud_config(state, use_create, use_write, ref_object, crud_model_id, model_id)"/>
</div>
<field name="crud_model_id"
on_change="on_change_crud_model_id(crud_model_id)"
attrs="{'invisible': ['|', ('state', '!=', 'object_create'), ('use_create', '!=', 'new_other')]}"/>
<field name="crud_model_name" invisible="1"/>
<label for="link_new_record" attrs="{'invisible': [('state', '!=', 'object_create')]}"/>
<div attrs="{'invisible': [('state', '!=', 'object_create')]}">
<field name="link_new_record" nolabel="1" style="display: inline-block;"/>
<p class="oe_grey oe_edit_only" style="display: inline-block; margin: 0px 0px 0px 8px;">
Check to attach the newly created record to the record on which the server action runs.
</p>
<group>
<field name="link_field_id"
domain="[('model_id', '=', model_id), ('relation', '=', crud_model_name), ('ttype', 'in', ['many2one'])]"
attrs="{'required': [('state', '=', 'object_create'), ('link_new_record', '=', True)],
'invisible': ['|', ('state', '!=', 'object_create'), ('link_new_record', '=', False)]}"/>
</group>
</form>
</field>
<field name="record_id" attrs="{'readonly':[('state','!=','object_create')]}" domain="[('model_id','in',[model_id])]"/>
<field name="write_id" attrs="{'readonly':[('state','!=','object_write')]}"/>
</group>
<label string="If you use a formula type, use a python expression using the variable 'object'." align="0.0"/>
</page>
<page string="Iteration Actions" attrs="{'invisible':[('state','!=','loop')]}">
<group col="4">
<field name="expression" attrs="{'required':[('state','=','loop')]}"/>
<field name="loop_action" domain="[('state','!=','loop')]" attrs="{'required':[('state','=','loop')]}"/>
</group>
</page>
<page string="Multi Actions" attrs="{'invisible':[('state','!=','other')]}">
<field name="child_ids"/>
<label string="Only one client action will be executed, last client action will be considered in case of multiple client actions." align="0.0"/>
</page>
</notebook>
<field name="type" readonly="1"/>
</div>
<label for="link_new_record" attrs="{'invisible': ['|', ('state', '!=', 'object_write'), ('use_write', '!=', 'expression')]}"/>
<div attrs="{'invisible': ['|', ('state', '!=', 'object_write'), ('use_write', '!=', 'expression')]}">
<p class="oe_grey oe_edit_only" style="margin: 0px;">
Write a python expression, beginning with object, that gives the record to update. An expression builder is available in the help tab. Examples:
</p>
<ul class="oe_grey oe_edit_only">
<li>object.partner_id</li>
<li>object.partner_id.currency_id</li>
</ul>
<field name="write_expression"
on_change="on_change_write_expression(write_expression, model_id)"
attrs="{'required': [('state', '=', 'object_write'), ('use_write', '=', 'expression')]}"/>
</div>
<field name="fields_lines">
<tree string="Field Mappings" editable="top">
<field name="col1" domain="[('model_id', '=', parent.crud_model_id)]"/>
<field name="type"/>
<field name="value"/>
</tree>
<form string="Field Mapping" version="7.0">
<group >
<field name="col1" domain="[('model_id', '=', parent.crud_model_id)]"/>
<field name="type"/>
<field name="value"/>
</group>
</form>
</field>
</group>
</page>
<page string="Execute several actions" autofocus="autofocus"
attrs="{'invisible': [('state', '!=', 'multi')]}">
<p class="oe_grey">
If several child actions return an action, only the last one will be executed.
This may happen when having server actions executing code that returns an action, or server actions returning a client action.
</p>
<field name="child_ids"
domain="[('model_id', '=', model_id)]"/>
</page>
<page string="Help">
<group>
<div style="margin-top: 4px;">
<h3>Help with Python expressions.</h3>
<p>Various fields may use Python code or Python expressions. The following variables can be used:</p>
<ul>
<li>self: ORM model of the record on which the action is triggered</li>
<li>object or obj: browse_record of the record on which the action is triggered</li>
<li>pool: ORM model pool (i.e. self.pool)</li>
<li>time: Python time module</li>
<li>cr: database cursor</li>
<li>uid: current user id</li>
<li>context: current context</li>
</ul>
<div>
<p>Example of condition expression using Python</p>
<ul>
<li>condition: True</li>
<li>condition: object.list_price > 5000</li>
</ul>
</div>
<div attrs="{'invisible': [('state', '!=', 'code')]}">
<p>Example of python code</p>
<code>
partner_name = obj.name + '_code'
self.pool["res.partner"].create(cr, uid, {"name": partner_name}, context=context)
</code>
</div>
</div>
<group>
<h3 colspan="2">Dynamic expression builder</h3>
<p colspan="2" attrs="{'invisible': [('model_id', '!=', False)]}">
Please set the Base Model of the action to enable the dynamic expression buidler.
</p>
<field name="model_object_field"
attrs="{'invisible': [('model_id', '=', False)]}"
domain="[('model_id', '=', model_id), ('ttype', '!=', 'one2many'), ('ttype', '!=', 'many2many')]"
on_change="onchange_sub_model_object_value_field(model_object_field)"/>
<field name="sub_object" readonly="1" attrs="{'invisible': [('model_id', '=', False)]}"/>
<field name="sub_model_object_field"
domain="[('model_id', '=', sub_object), ('ttype', '!=', 'one2many'), ('ttype', '!=', 'many2many')]"
attrs="{'readonly':[('sub_object','=',False)],
'required':[('sub_object','!=',False)],
'invisible': [('model_id', '=', False)]}"
on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field)"/>
<field name="copyvalue" attrs="{'invisible': [('model_id', '=', False)]}"/>
<h3 colspan="2">Find the ID of a record in the database</h3>
<field name="id_object" on_change="onchange_id_object(id_object)"/>
<field name="id_value" />
</group>
</group>
</page>
</notebook>
</sheet>
</form>
</field>
</record>

View File

@ -233,10 +233,10 @@ class ir_attachment(osv.osv):
targets = cr.dictfetchall()
model_attachments = {}
for target_dict in targets:
if not (target_dict['res_id'] and target_dict['res_model']):
if not target_dict['res_model']:
continue
# model_attachments = { 'model': { 'res_id': [id1,id2] } }
model_attachments.setdefault(target_dict['res_model'],{}).setdefault(target_dict['res_id'],set()).add(target_dict['id'])
model_attachments.setdefault(target_dict['res_model'],{}).setdefault(target_dict['res_id'] or 0, set()).add(target_dict['id'])
# To avoid multiple queries for each attachment found, checks are
# performed in batch as much as possible.
@ -250,7 +250,7 @@ class ir_attachment(osv.osv):
# filter ids according to what access rules permit
target_ids = targets.keys()
allowed_ids = self.pool[model].search(cr, uid, [('id', 'in', target_ids)], context=context)
allowed_ids = [0] + self.pool[model].search(cr, uid, [('id', 'in', target_ids)], context=context)
disallowed_ids = set(target_ids).difference(allowed_ids)
for res_id in disallowed_ids:
for attach_id in targets[res_id]:

View File

@ -70,7 +70,8 @@
<field name="name">ir.module.module.form</field>
<field name="model">ir.module.module</field>
<field name="arch" type="xml">
<form create="0" edit="0" string="Module" version="7.0" class='oe_styling_v8'>
<form create="0" edit="0" string="Module" version="7.0">
<link rel="stylesheet" href="/base/static/src/css/description.css"></link>
<sheet>
<field name="icon_image" widget="image" class="oe_avatar oe_left"/>
<div class="oe_title">
@ -127,9 +128,8 @@
<field name="reports_by_module"/>
</page>
</notebook>
<field name="description_html" class='oe_styling_v8'/>
</sheet>
<field name="description_html" class='oe_app_description oe_styling_v8'/>
<button name="button_immediate_install" states="uninstalled" string="Install" type="object" class="oe_highlight oe_center"/>
</form>
</field>
</record>

View File

@ -0,0 +1,2 @@
sass:
sass -t expanded --compass --watch --unix-newlines *.sass

View File

@ -0,0 +1,732 @@
@charset "utf-8";
/*
* This CSS is for the html description of modules
* TODO clean
*/
/* --------------------------------- *
* STYLING CONTEXT *
* --------------------------------- */
/* --- Styling for the V8/Lato/White/Purple design --- */
.openerp .oe_form_sheet_width {
max-width: 960px;
}
.openerp .oe_form .oe_styling_v8 {
width: 100%;
padding: 0;
margin: 0;
font-family: "Open Sans", "Helvetica", Sans;
font-weight: 300;
color: #646464;
background: white;
font-size: 16px;
}
.openerp .oe_form .oe_styling_v8 .oe_websiteonly {
display: none;
}
.openerp .oe_form .oe_styling_v8 .oe_website_contents {
background: whitesmoke;
padding-bottom: 1px;
}
.openerp .oe_form .oe_styling_v8 b {
font-weight: 600;
}
.openerp .oe_form .oe_styling_v8 a {
color: #6d57e0;
text-decoration: none;
}
.openerp .oe_form .oe_styling_v8 a:visited {
color: #5b284f;
}
.openerp .oe_form .oe_styling_v8 a:hover {
color: #0096eb;
}
.openerp .oe_form .oe_styling_v8 .oe_title_font {
font-family: "Lato", "Open Sans", "Helvetica", Sans;
}
.openerp .oe_form .oe_styling_v8 .oe_page {
background: white;
overflow: hidden;
-webkit-border-radius: 1px;
-moz-border-radius: 1px;
-ms-border-radius: 1px;
-o-border-radius: 1px;
border-radius: 1px;
-webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
-moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
}
.openerp .oe_form .oe_styling_v8 .oe_emph {
font-weight: 400;
}
.openerp .oe_form .oe_styling_v8 .oe_dark {
overflow: hidden;
background: #fcfcfc;
-webkit-box-shadow: 0px 5px 9px -7px rgba(0, 0, 255, 0.5) inset, 0px -3px 9px -7px rgba(0, 0, 255, 0.5) inset;
-moz-box-shadow: 0px 5px 9px -7px rgba(0, 0, 255, 0.5) inset, 0px -3px 9px -7px rgba(0, 0, 255, 0.5) inset;
box-shadow: 0px 5px 9px -7px rgba(0, 0, 255, 0.5) inset, 0px -3px 9px -7px rgba(0, 0, 255, 0.5) inset;
}
/* --------------------------------- *
* LAYOUT *
* --------------------------------- */
/* ------ BASE GRID CONSTRUCTS ----- */
.oe_page {
margin: 0px auto 64px auto;
max-width: 992px;
}
.oe_row {
width: 928px;
margin-top: 16px;
margin-bottom: 16px;
margin-left: auto;
margin-right: auto;
}
.oe_row.oe_fit {
width: auto;
}
.oe_clearfix:after, .oe_row:after {
content: ".";
display: block;
clear: both;
visibility: hidden;
line-height: 0;
height: 0;
}
[class*='oe_span'] {
float: left;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 0 16px;
}
.oe_span12 {
width: 928px;
}
.oe_span10 {
width: 773px;
}
.oe_span9 {
width: 696px;
}
.oe_span8 {
width: 618px;
}
.oe_span6 {
width: 464px;
}
.oe_span4 {
width: 309px;
}
.oe_span3 {
width: 232px;
}
.oe_span2 {
width: 154px;
}
[class*='oe_span'].oe_fit {
padding-left: 0px !important;
padding-right: 0px !important;
}
[class*='oe_span'].oe_right {
float: right;
}
.oe_row.oe_flex [class*='oe_span'] {
display: inline-block;
float: none;
vertical-align: top;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 0 16px;
width: auto;
}
.oe_row.oe_flex .oe_span12 {
max-width: 928px;
}
.oe_row.oe_flex .oe_span10 {
max-width: 769px;
}
.oe_row.oe_flex .oe_span9 {
max-width: 692px;
}
.oe_row.oe_flex .oe_span8 {
max-width: 614px;
}
.oe_row.oe_flex .oe_span6 {
max-width: 460px;
}
.oe_row.oe_flex .oe_span4 {
max-width: 305px;
}
.oe_row.oe_flex .oe_span3 {
max-width: 228px;
}
.oe_row.oe_flex .oe_span2 {
max-width: 150px;
}
.oe_mb0 {
margin-bottom: 0px !important;
}
.oe_mb4 {
margin-bottom: 4px !important;
}
.oe_mb8 {
margin-bottom: 8px !important;
}
.oe_mb16 {
margin-bottom: 16px !important;
}
.oe_mb32 {
margin-bottom: 32px !important;
}
.oe_mb48 {
margin-bottom: 48px !important;
}
.oe_mb64 {
margin-bottom: 64px !important;
}
.oe_mt0 {
margin-top: 0px !important;
}
.oe_mt4 {
margin-top: 4px !important;
}
.oe_mt8 {
margin-top: 8px !important;
}
.oe_mt16 {
margin-top: 16px !important;
}
.oe_mt32 {
margin-top: 32px !important;
}
.oe_mt48 {
margin-top: 48px !important;
}
.oe_mt64 {
margin-top: 64px !important;
}
/* ------ GENERIC LAYOUT MODIFIERS ----- */
.oe_rightfit {
padding-right: 0px !important;
}
.oe_leftfit {
padding-left: 0px !important;
}
.oe_leftalign {
text-align: left;
}
.oe_rightalign {
text-align: right;
}
.oe_centeralign {
text-align: center;
}
.oe_centered {
margin-left: auto;
margin-right: auto;
}
.oe_hidden {
display: none !important;
opacity: 0 !important;
}
.oe_invisible {
visibility: hidden !important;
}
.oe_transparent {
opacity: 0 !important;
}
.oe_mb0 {
margin-bottom: 0px !important;
}
.oe_mb4 {
margin-bottom: 4px !important;
}
.oe_mb8 {
margin-bottom: 8px !important;
}
.oe_mb16 {
margin-bottom: 16px !important;
}
.oe_mb32 {
margin-bottom: 32px !important;
}
.oe_mb64 {
margin-bottom: 64px !important;
}
.oe_spaced {
margin-top: 32px;
margin-bottom: 32px;
}
.oe_more_spaced {
margin-top: 64px;
margin-bottom: 64px;
}
.oe_padded {
padding-top: 16px;
padding-bottom: 16px;
}
.oe_more_padded {
padding-top: 32px;
padding-bottom: 32px;
}
/* --------------------------------- *
* WEBPAGE COMPONENTS *
* --------------------------------- */
/* ------ BUTTONS ----- */
.oe_button {
position: relative;
bottom: 0;
display: inline-block;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.oe_styling_v8 .oe_button, .oe_styling_v8 a.oe_button {
padding: 8px 14px;
background: #8b72b6;
color: white;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
-ms-border-radius: 2px;
-o-border-radius: 2px;
border-radius: 2px;
-webkit-box-shadow: 0px 2px 0px #afa8cc;
-moz-box-shadow: 0px 2px 0px #afa8cc;
box-shadow: 0px 2px 0px #afa8cc;
text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.44);
border: solid 1px rgba(0, 0, 0, 0.09);
-webkit-transition-property: bottom, background;
-moz-transition-property: bottom, background;
-o-transition-property: bottom, background;
transition-property: bottom, background;
-webkit-transition-duration: 250ms;
-moz-transition-duration: 250ms;
-o-transition-duration: 250ms;
transition-duration: 250ms;
}
.oe_styling_v8 .oe_button:hover, .oe_styling_v8 a.oe_button:hover {
background: #8b5bdd;
color: white;
}
.oe_styling_v8 .oe_button:active, .oe_styling_v8 a.oe_button:active {
background: #333333;
bottom: -3px;
}
.oe_styling_v8 .oe_button.oe_big, .oe_styling_v8 a.oe_button.oe_big {
font-size: 24px;
}
.oe_styling_v8 .oe_button.oe_bigger, .oe_styling_v8 a.oe_button.oe_bigger {
font-size: 32px;
}
.oe_styling_v8 .oe_button.oe_small, .oe_styling_v8 a.oe_button.oe_small {
font-size: 13px;
padding: 2px 4px;
}
.oe_styling_v8 .oe_button.oe_small:active, .oe_styling_v8 a.oe_button.oe_small:active {
bottom: -1px;
}
.oe_styling_v8 .oe_button.oe_medium, .oe_styling_v8 a.oe_button.oe_medium {
padding: 5px 12px;
font-size: 16px;
}
.oe_styling_v8 .oe_button.oe_tacky, .oe_styling_v8 a.oe_button.oe_tacky {
background: #ff4444;
-webkit-box-shadow: 0px 2px 0px #eba8a8;
-moz-box-shadow: 0px 2px 0px #eba8a8;
box-shadow: 0px 2px 0px #eba8a8;
}
.oe_styling_v8 .oe_button.oe_tacky:hover, .oe_styling_v8 a.oe_button.oe_tacky:hover {
background: #ff1010;
}
.oe_styling_v8 .oe_button.oe_tacky:active, .oe_styling_v8 a.oe_button.oe_tacky:active {
background: black;
}
.oe_styling_v8 .oe_button.oe_disabled, .oe_styling_v8 a.oe_button.oe_disabled {
background: #c8c8c8;
-webkit-box-shadow: 0px 2px 0px #b4b4b4;
-moz-box-shadow: 0px 2px 0px #b4b4b4;
box-shadow: 0px 2px 0px #b4b4b4;
cursor: default;
}
.oe_styling_v8 .oe_button.oe_disabled:hover, .oe_styling_v8 a.oe_button.oe_disabled:hover {
background: #c8c8c8;
-webkit-box-shadow: 0px 2px 0px #b4b4b4;
-moz-box-shadow: 0px 2px 0px #b4b4b4;
box-shadow: 0px 2px 0px #b4b4b4;
}
.oe_styling_v8 .oe_button.oe_disabled:active, .oe_styling_v8 a.oe_button.oe_disabled:active {
background: #c8c8c8;
bottom: 0px;
-webkit-box-shadow: 0px 2px 0px #b4b4b4;
-moz-box-shadow: 0px 2px 0px #b4b4b4;
box-shadow: 0px 2px 0px #b4b4b4;
}
.oe_styling_v8.oe_styling_black .oe_button {
-webkit-box-shadow: 0px 2px 0px #463555;
-moz-box-shadow: 0px 2px 0px #463555;
box-shadow: 0px 2px 0px #463555;
}
/* ------ FORMS ----- */
.oe_styling_v8 {
/* FIXME: this is a quick hack for the release */
}
.oe_styling_v8 .oe_input {
padding: 4px 7px;
border-radius: 3px;
border: solid 1px #d6d6d6;
box-shadow: 0px 2px #e6e6e6;
background: #fafafa;
font-weight: 300;
outline: none;
-webkit-transition: all 150ms linear;
-moz-transition: all 150ms linear;
-o-transition: all 150ms linear;
transition: all 150ms linear;
}
.oe_styling_v8 .oe_input:focus {
border: solid 1px #969696;
box-shadow: 0px 2px #d2d2d2;
}
.oe_styling_v8 .oe_input.oe_valid {
background: #f2ffec;
border-color: #b1ebb6;
box-shadow: 0px 2px #e1f8e1;
color: #0f610f;
}
.oe_styling_v8 .oe_input.oe_invalid {
background: #fff2f2;
border-color: #ebb1b1;
box-shadow: 0px 2px #f8e1e1;
color: #610f0f;
}
.oe_styling_v8 .oe_input.oe_big {
padding: 8px 14px;
}
.oe_styling_v8 .oe_input_label {
font-weight: 300;
font-size: 16px;
}
.oe_styling_v8 .oe_input_label.oe_big {
font-size: 20px;
}
.oe_styling_v8 .oe_textarea {
width: 300px;
height: 80px;
}
.oe_styling_v8 .oe_form_layout_table {
width: 100%;
}
.oe_styling_v8 .oe_form_layout_table td {
padding-bottom: 16px;
}
.oe_styling_v8 .oe_form_layout_table td:first-child {
text-align: right;
padding-right: 16px;
}
/* ------ SLOGANS ----- */
.oe_styling_v8 .oe_slogan {
color: #333333;
font-family: "Lato", "Open Sans", "Helvetica", Sans;
text-align: center;
margin-top: 32px;
margin-bottom: 32px;
}
.oe_styling_v8 h1.oe_slogan {
font-size: 64px;
font-weight: 900;
margin-top: 48px;
margin-bottom: 48px;
}
.oe_styling_v8 h2.oe_slogan {
font-size: 40px;
font-weight: 300;
}
.oe_styling_v8 h3.oe_slogan {
font-size: 26px;
font-weight: 300;
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50);
opacity: 0.5;
}
.oe_styling_v8 h4.oe_slogan {
font-size: 24px;
font-weight: 300;
}
.oe_styling_v8 h4.oe_slogan:before, .oe_styling_v8 h4.oe_slogan:after {
margin: 0 20px;
content: "";
display: inline-block;
width: 100px;
height: 0px;
border-top: solid 1px;
vertical-align: middle;
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=30);
opacity: 0.3;
}
.oe_styling_v8 h5.oe_slogan {
font-weight: 300;
}
/* ------ QUOTES ----- */
.oe_quote {
margin: 8px;
padding: 16px;
background: rgba(0, 0, 0, 0.02);
border: solid 1px rgba(0, 0, 0, 0.06);
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
-ms-border-radius: 2px;
-o-border-radius: 2px;
border-radius: 2px;
}
.oe_quote .oe_q, .oe_quote q {
margin: 10px;
display: block;
font-style: italic;
text-align: center;
font-size: 20px;
color: #4e66e7;
}
.oe_quote .oe_q:before, .oe_quote .oe_q:after, .oe_quote q:before, .oe_quote q:after {
content: '"';
font-weight: 900;
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=20);
opacity: 0.2;
}
.oe_quote cite {
display: block;
font-style: normal;
margin-top: 16px;
}
.oe_quote .oe_photo {
float: left;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
border-radius: 3px;
margin-right: 16px;
}
.oe_quote .oe_author {
font-size: 20px;
padding-top: 6px;
color: #8d7bac;
}
.oe_dark .oe_quote {
background: white;
border: 1px solid #f0f0ff;
}
/* ------ PICTURES ----- */
.oe_picture {
display: block;
max-width: 84%;
max-height: 400px;
margin: 16px 8%;
}
.oe_screenshot {
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
border-radius: 3px;
-webkit-box-shadow: 0px 3px 8px rgba(0, 0, 0, 0.2);
-moz-box-shadow: 0px 3px 8px rgba(0, 0, 0, 0.2);
box-shadow: 0px 3px 8px rgba(0, 0, 0, 0.2);
}
.oe_pic_ctr {
position: relative;
}
.oe_pic_ctr > img.oe_picture {
width: 100%;
max-width: none;
max-height: none;
margin: 0;
}
.oe_pic_ctr > .oe_title {
position: absolute;
top: 15px;
right: 38px;
}
.oe_styling_v8 .oe_pic_ctr > .oe_title {
font-size: 64px;
color: white;
font-weight: 600;
margin: 0;
text-shadow: 0px 2px 0px #494949, 0px 2px 5px rgba(0, 0, 0, 0.33), 0px 0px 60px rgba(0, 0, 0, 0.22);
}
/* ----- Link Image with Footer ----- */
/* FIXME: Terrible CSS, rewrite this */
div.oe_demo {
position: relative;
border: 1px solid #dedede;
}
div.oe_demo span.oe_demo_play {
top: 50%;
left: 50%;
width: 80px;
height: 60px;
margin-top: -30px;
margin-left: -40px;
display: block;
position: absolute;
background: url("../img/layout/play-button.png") no-repeat left top transparent;
pointer-events: none;
}
div.oe_demo img {
max-width: 100%;
width: 100%;
}
div.oe_demo div.oe_demo_footer {
position: absolute;
left: 0;
background-color: rgba(0, 0, 0, 0.4);
opacity: 0.85;
bottom: -1px;
width: 100%;
padding-top: 7px;
padding-bottom: 7px;
color: white;
font-size: 14px;
font-weight: bold;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
pointer-events: none;
}
div.oe_demo:hover span.oe_demo_play {
background: url("../img/layout/play-button-over.png") no-repeat left top transparent;
}
/* ----- SEPARATOR ----- */
.oe_styling_v8 .oe_container.oe_separator {
height: 64px;
margin-bottom: 16px;
background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, rgba(0, 0, 0, 0)), color-stop(100%, rgba(0, 0, 0, 0.02)));
background: -webkit-linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.02));
background: -moz-linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.02));
background: -o-linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.02));
background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.02));
-webkit-box-shadow: 0px -3px 10px -5px rgba(0, 0, 0, 0.1) inset;
-moz-box-shadow: 0px -3px 10px -5px rgba(0, 0, 0, 0.1) inset;
box-shadow: 0px -3px 10px -5px rgba(0, 0, 0, 0.1) inset;
overflow-y: hidden;
}
/* ----- TABS ----- */
.oe_row_tabs {
text-align: center;
margin-top: 0px;
margin-bottom: 0px;
padding-top: 21px;
}
.oe_row_tab {
position: relative;
min-width: 120px;
padding: 8px;
font-size: 20px;
display: inline-block;
margin: 0px -2px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
border: solid 1px rgba(0, 0, 0, 0.1);
border-bottom: none;
background: #fafafa;
background-image: +linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.02));
box-shadow: 0px -3px 10px -5px rgba(0, 0, 0, 0.1) inset;
cursor: pointer;
-webkit-transition: all 250ms linear;
-moz-transition: all 250ms linear;
-o-transition: all 250ms linear;
transition: all 250ms linear;
}
.oe_row_tab:hover {
padding-bottom: 12px;
top: -4px;
background-color: white;
}
.oe_row_tab.oe_active {
background-color: white;
background-image: none;
box-shadow: none;
border-top-color: #8272b6;
border-top-width: 2px;
cursor: default;
}
.oe_row_tab.oe_active:hover {
padding-bottom: 8px;
top: 0asx;
}
/* ------ CALL TO ACTION ----- */
.oe_calltoaction {
height: 32px;
margin-top: -32px;
position: relative;
}

View File

@ -0,0 +1,572 @@
@charset "utf-8"
@import "compass/css3"
@import "compass/css3/user-interface"
/**
* This CSS is for the html description of modules
* TODO clean
*/
/* --------------------------------- *
* STYLING CONTEXT *
* --------------------------------- */
/* --- Styling for the V8/Lato/White/Purple design --- */
$v8_title_font_color: rgb(51,51,51)
$v8_text_font_color: rgb(100,100,100)
$v8_font_paragraph_color: rgb(51,51,51)
$v8_body_color: rgb(245,245,245)
$v8_bg_color: rgb(255,255,255)
$v8_text_font_family: 'Open Sans','Helvetica',Sans
$v8_title_font_family: 'Lato','Open Sans','Helvetica',Sans
$v8_anchor_color: #6D57E0
$v8_anchor_visited_color: rgb(91, 40, 79)
.openerp .oe_form_sheet_width
max-width: 960px
.openerp .oe_form .oe_styling_v8
width: 100%
padding: 0
margin: 0
font-family: $v8_text_font_family
font-weight: 300
color: $v8_text_font_color
background: $v8_bg_color
font-size: 16px
.oe_websiteonly
display: none
.oe_website_contents
background: $v8_body_color
padding-bottom: 1px
b
font-weight: 600
a
color: $v8_anchor_color
text-decoration: none
a:visited
color: $v8_anchor_visited_color
a:hover
color: #0096EB
.oe_title_font
font-family: $v8_title_font_family
.oe_page
background: $v8_bg_color
overflow: hidden
+border-radius(1px)
+box-shadow(0 1px 3px rgba(0,0,0,0.35))
.oe_emph
font-weight: 400
.oe_dark
overflow: hidden
background: #FCFCFC
+box-shadow(0px 5px 9px -7px rgba(0,0,255,0.5) inset, 0px -3px 9px -7px rgba(0,0,255,0.5) inset)
/* --------------------------------- *
* LAYOUT *
* --------------------------------- */
/* ------ BASE GRID CONSTRUCTS ----- */
.oe_page
margin: 0px auto 64px auto
max-width: 992px
.oe_row
width: 928px
margin-top: 16px
margin-bottom: 16px
margin-left: auto
margin-right: auto
.oe_row.oe_fit
width: auto
.oe_clearfix:after, .oe_row:after
content: "."
display: block
clear: both
visibility: hidden
line-height: 0
height: 0
$oe_span12_width: 928px
$oe_span10_width: 773px
$oe_span9_width: 696px
$oe_span8_width: 618px
$oe_span6_width: 464px
$oe_span4_width: 309px
$oe_span3_width: 232px
$oe_span2_width: 154px
[class*='oe_span']
float: left
+box-sizing(border-box)
padding: 0 16px
.oe_span12
width: $oe_span12_width
.oe_span10
width: $oe_span10_width
.oe_span9
width: $oe_span9_width
.oe_span8
width: $oe_span8_width
.oe_span6
width: $oe_span6_width
.oe_span4
width: $oe_span4_width
.oe_span3
width: $oe_span3_width
.oe_span2
width: $oe_span2_width
[class*='oe_span'].oe_fit
padding-left: 0px !important
padding-right: 0px !important
[class*='oe_span'].oe_right
float: right
.oe_row.oe_flex
[class*='oe_span']
display: inline-block
float: none
vertical-align: top
+box-sizing(border-box)
padding: 0 16px
width: auto
.oe_span12
max-width: $oe_span12_width
.oe_span10
max-width: ($oe_span10_width +-4px)
.oe_span9
max-width: ($oe_span9_width +-4px)
.oe_span8
max-width: ($oe_span8_width +-4px)
.oe_span6
max-width: ($oe_span6_width +-4px)
.oe_span4
max-width: ($oe_span4_width +-4px)
.oe_span3
max-width: ($oe_span3_width +-4px)
.oe_span2
max-width: ($oe_span2_width +-4px)
.oe_mb0
margin-bottom: 0px !important
.oe_mb4
margin-bottom: 4px !important
.oe_mb8
margin-bottom: 8px !important
.oe_mb16
margin-bottom: 16px !important
.oe_mb32
margin-bottom: 32px !important
.oe_mb48
margin-bottom: 48px !important
.oe_mb64
margin-bottom: 64px !important
.oe_mt0
margin-top: 0px !important
.oe_mt4
margin-top: 4px !important
.oe_mt8
margin-top: 8px !important
.oe_mt16
margin-top: 16px !important
.oe_mt32
margin-top: 32px !important
.oe_mt48
margin-top: 48px !important
.oe_mt64
margin-top: 64px !important
/* ------ GENERIC LAYOUT MODIFIERS ----- */
.oe_rightfit
padding-right: 0px !important
.oe_leftfit
padding-left: 0px !important
.oe_leftalign
text-align: left
.oe_rightalign
text-align: right
.oe_centeralign
text-align: center
.oe_centered
margin-left: auto
margin-right: auto
.oe_hidden
display: none !important
opacity: 0 !important
.oe_invisible
visibility: hidden !important
.oe_transparent
opacity: 0 !important
.oe_mb0
margin-bottom: 0px !important
.oe_mb4
margin-bottom: 4px !important
.oe_mb8
margin-bottom: 8px !important
.oe_mb16
margin-bottom: 16px !important
.oe_mb32
margin-bottom: 32px !important
.oe_mb64
margin-bottom: 64px !important
.oe_spaced
margin-top: 32px
margin-bottom: 32px
.oe_more_spaced
margin-top: 64px
margin-bottom: 64px
.oe_padded
padding-top: 16px
padding-bottom: 16px
.oe_more_padded
padding-top: 32px
padding-bottom: 32px
/* --------------------------------- *
* WEBPAGE COMPONENTS *
* --------------------------------- */
/* ------ BUTTONS ----- */
.oe_button
position: relative
bottom: 0
display: inline-block
cursor: pointer
+user-select(none)
.oe_styling_v8 .oe_button, .oe_styling_v8 a.oe_button
padding: 8px 14px
background: rgb(139, 114, 182)
color: white
+border-radius(2px)
+box-shadow(0px 2px 0px rgb(175, 168, 204))
+text-shadow(0px 1px 1px rgba(0,0,0, 0.44))
border: solid 1px rgba(0,0,0,0.09)
+transition-property((bottom, background))
+transition-duration(250ms)
&:hover
background: rgb(139,91,221)
color: white
&:active
background: rgb(51,51,51)
bottom: -3px
&.oe_big
font-size: 24px
&.oe_bigger
font-size: 32px
&.oe_small
font-size: 13px
padding: 2px 4px
&:active
bottom: -1px
&.oe_medium
padding: 5px 12px
font-size: 16px
&.oe_tacky
background: rgb(255,68,68)
+box-shadow(0px 2px 0px #eba8a8)
&:hover
background: rgb(255,16,16)
&:active
background: black
&.oe_disabled
background: rgb(200,200,200)
+box-shadow(0px 2px 0px rgb(180,180,180))
cursor: default
&:hover
background: rgb(200,200,200)
+box-shadow(0px 2px 0px rgb(180,180,180))
&:active
background: rgb(200,200,200)
bottom: 0px
+box-shadow(0px 2px 0px rgb(180,180,180))
.oe_styling_v8.oe_styling_black .oe_button
+box-shadow(0px 2px 0px rgb(70,53,85))
/* ------ FORMS ----- */
.oe_styling_v8
.oe_input
padding: 4px 7px
border-radius: 3px
border: solid 1px rgb(214,214,214)
box-shadow: 0px 2px rgb(230,230,230)
background: rgb(250,250,250)
font-weight: 300
outline: none
@include transition( all 150ms linear )
&:focus
border: solid 1px rgb(150,150,150)
box-shadow: 0px 2px rgb(210,210,210)
&.oe_valid
background: #F2FFEC
border-color: rgb(177,235,182)
box-shadow: 0px 2px rgb(225,248,225)
color: rgb(15,97,15)
&.oe_invalid
background: rgb(255,242,242)
border-color: #EBB1B1
box-shadow: 0px 2px #F8E1E1
color: #610F0F
&.oe_big
padding: 8px 14px
.oe_input_label
font-weight: 300
font-size: 16px
&.oe_big
font-size: 20px
/* FIXME: this is a quick hack for the release */
.oe_textarea
width: 300px
height: 80px
.oe_form_layout_table
width: 100%
td
padding-bottom: 16px
&:first-child
text-align: right
padding-right: 16px
/* ------ SLOGANS ----- */
.oe_styling_v8
.oe_slogan
color: $v8_title_font_color
font-family: $v8_title_font_family
text-align: center
margin-top: 32px
margin-bottom: 32px
h1.oe_slogan
font-size: 64px
font-weight: 900
margin-top: 48px
margin-bottom: 48px
h2.oe_slogan
font-size: 40px
font-weight: 300
h3.oe_slogan
font-size: 26px
font-weight: 300
+opacity(0.5)
h4.oe_slogan
font-size: 24px
font-weight: 300
h4.oe_slogan:before, h4.oe_slogan:after
margin: 0 20px
content: ""
display: inline-block
width: 100px
height: 0px
border-top: solid 1px
vertical-align: middle
+opacity(0.3)
h5.oe_slogan
font-weight: 300
//TODO
/* ------ QUOTES ----- */
.oe_quote
margin: 8px
padding: 16px
background: rgba(0,0,0,0.02)
border: solid 1px rgba(0,0,0,0.06)
+border-radius(2px)
.oe_q,q
margin: 10px
display: block
font-style: italic
text-align: center
font-size: 20px
color: rgb(78, 102, 231)
&:before, &:after
content: '\"'
font-weight: 900
+opacity(0.2)
cite
display: block
font-style: normal
margin-top: 16px
.oe_photo
float: left
+border-radius(3px)
margin-right: 16px
.oe_author
font-size: 20px
padding-top: 6px
color: rgb(141, 123, 172)
.oe_dark .oe_quote
background: white
border: 1px solid rgb(240,240,255)
/* ------ PICTURES ----- */
// display a picture in a span
.oe_picture
display: block
max-width: 84%
max-height: 400px
margin: 16px 8%
// style the picture like a screenshot
.oe_screenshot
+border-radius(3px)
+box-shadow(0px 3px 8px rgba(0,0,0,0.2))
// display a picture taking full width of a row
.oe_pic_ctr
position: relative
.oe_pic_ctr > img.oe_picture
width: 100%
max-width: none
max-height: none
margin: 0
// styling of the picture's title
.oe_pic_ctr > .oe_title
position: absolute
top: 15px
right: 38px
.oe_styling_v8 .oe_pic_ctr > .oe_title
font-size: 64px
color: white
font-weight: 600
margin: 0
+text-shadow( 0px 2px 0px rgb(73, 73, 73), 0px 2px 5px rgba(0, 0, 0, 0.33), 0px 0px 60px rgba(0, 0, 0, 0.22))
/* ----- Link Image with Footer ----- */
/* FIXME: Terrible CSS, rewrite this */
div.oe_demo
position: relative
border: 1px solid #dedede
span.oe_demo_play
top: 50%
left: 50%
width: 80px
height: 60px
margin-top: -30px
margin-left: -40px
display: block
position: absolute
background: url("../img/layout/play-button.png") no-repeat left top transparent
pointer-events: none
img
max-width: 100%
width: 100%
div.oe_demo_footer
position: absolute
left: 0
background-color: rgba(0,0,0,0.4)
opacity: 0.85
bottom: -1px
width: 100%
padding-top: 7px
padding-bottom: 7px
color: white
font-size: 14px
font-weight: bold
border-bottom-left-radius: 3px
border-bottom-right-radius: 3px
pointer-events: none
div.oe_demo:hover
span.oe_demo_play
background: url("../img/layout/play-button-over.png") no-repeat left top transparent
/* ----- SEPARATOR ----- */
.oe_styling_v8 .oe_container.oe_separator
height: 64px
margin-bottom: 16px
@include background(linear-gradient(rgba(0,0,0,0),rgba(0,0,0,0.02)))
+box-shadow(0px -3px 10px -5px rgba(0,0,0,0.1) inset)
overflow-y: hidden
/* ----- TABS ----- */
.oe_row_tabs
text-align: center
margin-top: 0px
margin-bottom: 0px
padding-top: 21px
.oe_row_tab
position: relative
min-width: 120px
padding: 8px
font-size: 20px
display: inline-block
margin: 0px -2px
border-top-left-radius: 4px
border-top-right-radius: 4px
border: solid 1px rgba(0,0,0,0.1)
border-bottom: none
background: rgb(250,250,250)
background-image: +linear-gradient(rgba(0,0,0,0), rgba(0,0,0,0.02))
box-shadow: 0px -3px 10px -5px rgba(0,0,0,0.1) inset
cursor: pointer
@include transition(all 250ms linear)
.oe_row_tab:hover
padding-bottom: 12px
top: -4px
background-color: white
.oe_row_tab.oe_active
background-color: white
background-image: none
box-shadow: none
border-top-color: rgb(130, 114, 182)
border-top-width: 2px
cursor: default
.oe_row_tab.oe_active:hover
padding-bottom: 8px
top: 0asx
/* ------ CALL TO ACTION ----- */
.oe_calltoaction
height: 32px
margin-top: -32px
position: relative

View File

@ -92,7 +92,8 @@ openerp.base = function(instance) {
start: function() {
var self = this;
return self.get_client().
// desactivated for now because apps does not work anyway due to changes in the framework
/*return self.get_client().
done(function(client) {
client.replace(self.$el).
done(function() {
@ -100,13 +101,13 @@ openerp.base = function(instance) {
client.do_action(self.remote_action_id, {hide_breadcrumb: true});
});
}).
fail(function(client) {
fail(function(client) {*/
self.do_warn(_t('OpenERP Apps Unreachable'), _t('Showing locally available modules'), true);
self.rpc('/web/action/load', {action_id: self.failback_action_id}).done(function(action) {
self.do_action(action);
instance.webclient.menu.open_action(action.id);
});
});
//});
},
});

View File

@ -1,5 +1,6 @@
import test_base
import test_expression
import test_ir_actions
import test_ir_attachment
import test_ir_values
import test_menu
@ -10,6 +11,7 @@ import test_search
checks = [
test_base,
test_expression,
test_ir_actions,
test_ir_attachment,
test_ir_values,
test_menu,

View File

@ -0,0 +1,398 @@
import unittest2
from openerp.osv.orm import except_orm
import openerp.tests.common as common
from openerp.tools import mute_logger
class TestServerActionsBase(common.TransactionCase):
def setUp(self):
super(TestServerActionsBase, self).setUp()
cr, uid = self.cr, self.uid
# Models
self.ir_actions_server = self.registry('ir.actions.server')
self.ir_actions_client = self.registry('ir.actions.client')
self.ir_values = self.registry('ir.values')
self.ir_model = self.registry('ir.model')
self.ir_model_fields = self.registry('ir.model.fields')
self.res_partner = self.registry('res.partner')
self.res_country = self.registry('res.country')
# Data on which we will run the server action
self.test_country_id = self.res_country.create(cr, uid, {
'name': 'TestingCountry',
'code': 'TY',
'address_format': 'SuperFormat',
})
self.test_country = self.res_country.browse(cr, uid, self.test_country_id)
self.test_partner_id = self.res_partner.create(cr, uid, {
'name': 'TestingPartner',
'city': 'OrigCity',
'country_id': self.test_country_id,
})
self.test_partner = self.res_partner.browse(cr, uid, self.test_partner_id)
self.context = {
'active_id': self.test_partner_id,
'active_model': 'res.partner',
}
# Model data
self.res_partner_model_id = self.ir_model.search(cr, uid, [('model', '=', 'res.partner')])[0]
self.res_partner_name_field_id = self.ir_model_fields.search(cr, uid, [('model', '=', 'res.partner'), ('name', '=', 'name')])[0]
self.res_partner_city_field_id = self.ir_model_fields.search(cr, uid, [('model', '=', 'res.partner'), ('name', '=', 'city')])[0]
self.res_partner_country_field_id = self.ir_model_fields.search(cr, uid, [('model', '=', 'res.partner'), ('name', '=', 'country_id')])[0]
self.res_partner_parent_field_id = self.ir_model_fields.search(cr, uid, [('model', '=', 'res.partner'), ('name', '=', 'parent_id')])[0]
self.res_country_model_id = self.ir_model.search(cr, uid, [('model', '=', 'res.country')])[0]
self.res_country_name_field_id = self.ir_model_fields.search(cr, uid, [('model', '=', 'res.country'), ('name', '=', 'name')])[0]
self.res_country_code_field_id = self.ir_model_fields.search(cr, uid, [('model', '=', 'res.country'), ('name', '=', 'code')])[0]
# create server action to
self.act_id = self.ir_actions_server.create(cr, uid, {
'name': 'TestAction',
'condition': 'True',
'model_id': self.res_partner_model_id,
'state': 'code',
'code': 'obj.write({"comment": "MyComment"})',
})
class TestServerActions(TestServerActionsBase):
def test_00_action(self):
cr, uid = self.cr, self.uid
# Do: eval 'True' condition
self.ir_actions_server.run(cr, uid, [self.act_id], self.context)
self.test_partner.refresh()
self.assertEqual(self.test_partner.comment, 'MyComment', 'ir_actions_server: invalid condition check')
self.test_partner.write({'comment': False})
# Do: eval False condition, that should be considered as True (void = True)
self.ir_actions_server.write(cr, uid, [self.act_id], {'condition': False})
self.ir_actions_server.run(cr, uid, [self.act_id], self.context)
self.test_partner.refresh()
self.assertEqual(self.test_partner.comment, 'MyComment', 'ir_actions_server: invalid condition check')
# Do: create contextual action
self.ir_actions_server.create_action(cr, uid, [self.act_id])
# Test: ir_values created
ir_values_ids = self.ir_values.search(cr, uid, [('name', '=', 'Run TestAction')])
self.assertEqual(len(ir_values_ids), 1, 'ir_actions_server: create_action should have created an entry in ir_values')
ir_value = self.ir_values.browse(cr, uid, ir_values_ids[0])
self.assertEqual(ir_value.value, 'ir.actions.server,%s' % self.act_id, 'ir_actions_server: created ir_values should reference the server action')
self.assertEqual(ir_value.model, 'res.partner', 'ir_actions_server: created ir_values should be linked to the action base model')
# Do: remove contextual action
self.ir_actions_server.unlink_action(cr, uid, [self.act_id])
# Test: ir_values removed
ir_values_ids = self.ir_values.search(cr, uid, [('name', '=', 'Run TestAction')])
self.assertEqual(len(ir_values_ids), 0, 'ir_actions_server: unlink_action should remove the ir_values record')
def test_10_code(self):
cr, uid = self.cr, self.uid
self.ir_actions_server.write(cr, uid, self.act_id, {
'state': 'code',
'code': """partner_name = obj.name + '_code'
self.pool["res.partner"].create(cr, uid, {"name": partner_name}, context=context)"""
})
run_res = self.ir_actions_server.run(cr, uid, [self.act_id], context=self.context)
self.assertFalse(run_res, 'ir_actions_server: code server action correctly finished should return False')
pids = self.res_partner.search(cr, uid, [('name', 'ilike', 'TestingPartner_code')])
self.assertEqual(len(pids), 1, 'ir_actions_server: 1 new partner should have been created')
def test_20_trigger(self):
cr, uid = self.cr, self.uid
# Data: code server action (at this point code-based actions should work)
act_id2 = self.ir_actions_server.create(cr, uid, {
'name': 'TestAction2',
'type': 'ir.actions.server',
'condition': 'True',
'model_id': self.res_partner_model_id,
'state': 'code',
'code': 'obj.write({"comment": "MyComment"})',
})
act_id3 = self.ir_actions_server.create(cr, uid, {
'name': 'TestAction3',
'type': 'ir.actions.server',
'condition': 'True',
'model_id': self.res_country_model_id,
'state': 'code',
'code': 'obj.write({"code": "ZZ"})',
})
# Data: create workflows
partner_wf_id = self.registry('workflow').create(cr, uid, {
'name': 'TestWorkflow',
'osv': 'res.partner',
'on_create': True,
})
partner_act1_id = self.registry('workflow.activity').create(cr, uid, {
'name': 'PartnerStart',
'wkf_id': partner_wf_id,
'flow_start': True
})
partner_act2_id = self.registry('workflow.activity').create(cr, uid, {
'name': 'PartnerTwo',
'wkf_id': partner_wf_id,
'kind': 'function',
'action': 'True',
'action_id': act_id2,
})
partner_trs1_id = self.registry('workflow.transition').create(cr, uid, {
'signal': 'partner_trans',
'act_from': partner_act1_id,
'act_to': partner_act2_id
})
country_wf_id = self.registry('workflow').create(cr, uid, {
'name': 'TestWorkflow',
'osv': 'res.country',
'on_create': True,
})
country_act1_id = self.registry('workflow.activity').create(cr, uid, {
'name': 'CountryStart',
'wkf_id': country_wf_id,
'flow_start': True
})
country_act2_id = self.registry('workflow.activity').create(cr, uid, {
'name': 'CountryTwo',
'wkf_id': country_wf_id,
'kind': 'function',
'action': 'True',
'action_id': act_id3,
})
country_trs1_id = self.registry('workflow.transition').create(cr, uid, {
'signal': 'country_trans',
'act_from': country_act1_id,
'act_to': country_act2_id
})
# Data: re-create country and partner to benefit from the workflows
self.test_country_id = self.res_country.create(cr, uid, {
'name': 'TestingCountry2',
'code': 'T2',
})
self.test_country = self.res_country.browse(cr, uid, self.test_country_id)
self.test_partner_id = self.res_partner.create(cr, uid, {
'name': 'TestingPartner2',
'country_id': self.test_country_id,
})
self.test_partner = self.res_partner.browse(cr, uid, self.test_partner_id)
self.context = {
'active_id': self.test_partner_id,
'active_model': 'res.partner',
}
# Run the action on partner object itself ('base')
self.ir_actions_server.write(cr, uid, [self.act_id], {
'state': 'trigger',
'use_relational_model': 'base',
'wkf_model_id': self.res_partner_model_id,
'wkf_model_name': 'res.partner',
'wkf_transition_id': partner_trs1_id,
})
self.ir_actions_server.run(cr, uid, [self.act_id], self.context)
self.test_partner.refresh()
self.assertEqual(self.test_partner.comment, 'MyComment', 'ir_actions_server: incorrect signal trigger')
# Run the action on related country object ('relational')
self.ir_actions_server.write(cr, uid, [self.act_id], {
'use_relational_model': 'relational',
'wkf_model_id': self.res_country_model_id,
'wkf_model_name': 'res.country',
'wkf_field_id': self.res_partner_country_field_id,
'wkf_transition_id': country_trs1_id,
})
self.ir_actions_server.run(cr, uid, [self.act_id], self.context)
self.test_country.refresh()
self.assertEqual(self.test_country.code, 'ZZ', 'ir_actions_server: incorrect signal trigger')
# Clear workflow cache, otherwise openerp will try to create workflows even if it has been deleted
from openerp.workflow import clear_cache
clear_cache(cr, uid)
def test_30_client(self):
cr, uid = self.cr, self.uid
client_action_id = self.registry('ir.actions.client').create(cr, uid, {
'name': 'TestAction2',
'tag': 'Test',
})
self.ir_actions_server.write(cr, uid, [self.act_id], {
'state': 'client_action',
'action_id': client_action_id,
})
res = self.ir_actions_server.run(cr, uid, [self.act_id], context=self.context)
self.assertEqual(res['name'], 'TestAction2', 'ir_actions_server: incorrect return result for a client action')
def test_40_crud_create(self):
cr, uid = self.cr, self.uid
_city = 'TestCity'
_name = 'TestNew'
# Do: create a new record in the same model and link it
self.ir_actions_server.write(cr, uid, [self.act_id], {
'state': 'object_create',
'use_create': 'new',
'link_new_record': True,
'link_field_id': self.res_partner_parent_field_id,
'fields_lines': [(0, 0, {'col1': self.res_partner_name_field_id, 'value': _name}),
(0, 0, {'col1': self.res_partner_city_field_id, 'value': _city})],
})
run_res = self.ir_actions_server.run(cr, uid, [self.act_id], context=self.context)
self.assertFalse(run_res, 'ir_actions_server: create record action correctly finished should return False')
# Test: new partner created
pids = self.res_partner.search(cr, uid, [('name', 'ilike', _name)])
self.assertEqual(len(pids), 1, 'ir_actions_server: TODO')
partner = self.res_partner.browse(cr, uid, pids[0])
self.assertEqual(partner.city, _city, 'ir_actions_server: TODO')
# Test: new partner linked
self.test_partner.refresh()
self.assertEqual(self.test_partner.parent_id.id, pids[0], 'ir_actions_server: TODO')
# Do: copy current record
self.ir_actions_server.write(cr, uid, [self.act_id], {'fields_lines': [[5]]})
self.ir_actions_server.write(cr, uid, [self.act_id], {
'state': 'object_create',
'use_create': 'copy_current',
'link_new_record': False,
'fields_lines': [(0, 0, {'col1': self.res_partner_name_field_id, 'value': 'TestCopyCurrent'}),
(0, 0, {'col1': self.res_partner_city_field_id, 'value': 'TestCity'})],
})
run_res = self.ir_actions_server.run(cr, uid, [self.act_id], context=self.context)
self.assertFalse(run_res, 'ir_actions_server: create record action correctly finished should return False')
# Test: new partner created
pids = self.res_partner.search(cr, uid, [('name', 'ilike', 'TestingPartner (copy)')]) # currently res_partner overrides default['name'] whatever its value
self.assertEqual(len(pids), 1, 'ir_actions_server: TODO')
partner = self.res_partner.browse(cr, uid, pids[0])
self.assertEqual(partner.city, 'TestCity', 'ir_actions_server: TODO')
self.assertEqual(partner.country_id.id, self.test_partner.country_id.id, 'ir_actions_server: TODO')
# Do: create a new record in another model
self.ir_actions_server.write(cr, uid, [self.act_id], {'fields_lines': [[5]]})
self.ir_actions_server.write(cr, uid, [self.act_id], {
'state': 'object_create',
'use_create': 'new_other',
'crud_model_id': self.res_country_model_id,
'link_new_record': False,
'fields_lines': [(0, 0, {'col1': self.res_country_name_field_id, 'value': 'obj.name', 'type': 'equation'}),
(0, 0, {'col1': self.res_country_code_field_id, 'value': 'obj.name[0:2]', 'type': 'equation'})],
})
run_res = self.ir_actions_server.run(cr, uid, [self.act_id], context=self.context)
self.assertFalse(run_res, 'ir_actions_server: create record action correctly finished should return False')
# Test: new country created
cids = self.res_country.search(cr, uid, [('name', 'ilike', 'TestingPartner')])
self.assertEqual(len(cids), 1, 'ir_actions_server: TODO')
country = self.res_country.browse(cr, uid, cids[0])
self.assertEqual(country.code, 'TE', 'ir_actions_server: TODO')
# Do: copy a record in another model
self.ir_actions_server.write(cr, uid, [self.act_id], {'fields_lines': [[5]]})
self.ir_actions_server.write(cr, uid, [self.act_id], {
'state': 'object_create',
'use_create': 'copy_other',
'crud_model_id': self.res_country_model_id,
'link_new_record': False,
'ref_object': 'res.country,%s' % self.test_country_id,
'fields_lines': [(0, 0, {'col1': self.res_country_name_field_id, 'value': 'NewCountry', 'type': 'value'}),
(0, 0, {'col1': self.res_country_code_field_id, 'value': 'NY', 'type': 'value'})],
})
run_res = self.ir_actions_server.run(cr, uid, [self.act_id], context=self.context)
self.assertFalse(run_res, 'ir_actions_server: create record action correctly finished should return False')
# Test: new country created
cids = self.res_country.search(cr, uid, [('name', 'ilike', 'NewCountry')])
self.assertEqual(len(cids), 1, 'ir_actions_server: TODO')
country = self.res_country.browse(cr, uid, cids[0])
self.assertEqual(country.code, 'NY', 'ir_actions_server: TODO')
self.assertEqual(country.address_format, 'SuperFormat', 'ir_actions_server: TODO')
def test_50_crud_write(self):
cr, uid = self.cr, self.uid
_name = 'TestNew'
# Do: create a new record in the same model and link it
self.ir_actions_server.write(cr, uid, [self.act_id], {
'state': 'object_write',
'use_write': 'current',
'fields_lines': [(0, 0, {'col1': self.res_partner_name_field_id, 'value': _name})],
})
run_res = self.ir_actions_server.run(cr, uid, [self.act_id], context=self.context)
self.assertFalse(run_res, 'ir_actions_server: create record action correctly finished should return False')
# Test: new partner created
pids = self.res_partner.search(cr, uid, [('name', 'ilike', _name)])
self.assertEqual(len(pids), 1, 'ir_actions_server: TODO')
partner = self.res_partner.browse(cr, uid, pids[0])
self.assertEqual(partner.city, 'OrigCity', 'ir_actions_server: TODO')
# Do: copy current record
self.ir_actions_server.write(cr, uid, [self.act_id], {'fields_lines': [[5]]})
self.ir_actions_server.write(cr, uid, [self.act_id], {
'use_write': 'other',
'crud_model_id': self.res_country_model_id,
'ref_object': 'res.country,%s' % self.test_country_id,
'fields_lines': [(0, 0, {'col1': self.res_country_name_field_id, 'value': 'obj.name', 'type': 'equation'})],
})
run_res = self.ir_actions_server.run(cr, uid, [self.act_id], context=self.context)
self.assertFalse(run_res, 'ir_actions_server: create record action correctly finished should return False')
# Test: new country created
cids = self.res_country.search(cr, uid, [('name', 'ilike', 'TestNew')])
self.assertEqual(len(cids), 1, 'ir_actions_server: TODO')
# Do: copy a record in another model
self.ir_actions_server.write(cr, uid, [self.act_id], {'fields_lines': [[5]]})
self.ir_actions_server.write(cr, uid, [self.act_id], {
'use_write': 'expression',
'crud_model_id': self.res_country_model_id,
'write_expression': 'object.country_id',
'fields_lines': [(0, 0, {'col1': self.res_country_name_field_id, 'value': 'NewCountry', 'type': 'value'})],
})
run_res = self.ir_actions_server.run(cr, uid, [self.act_id], context=self.context)
self.assertFalse(run_res, 'ir_actions_server: create record action correctly finished should return False')
# Test: new country created
cids = self.res_country.search(cr, uid, [('name', 'ilike', 'NewCountry')])
self.assertEqual(len(cids), 1, 'ir_actions_server: TODO')
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
def test_60_multi(self):
cr, uid = self.cr, self.uid
# Data: 2 server actions that will be nested
act1_id = self.ir_actions_server.create(cr, uid, {
'name': 'Subaction1',
'model_id': self.res_partner_model_id,
'state': 'code',
'code': 'action = {"type": "ir.actions.act_window"}',
})
# Do: create a new record in the same model and link it
act2_id = self.ir_actions_server.create(cr, uid, {
'name': 'Subaction2',
'model_id': self.res_partner_model_id,
'state': 'object_create',
'use_create': 'copy_current',
})
self.ir_actions_server.write(cr, uid, [self.act_id], {
'state': 'multi',
'child_ids': [(6, 0, [act1_id, act2_id])],
})
# Do: run the action
res = self.ir_actions_server.run(cr, uid, [self.act_id], context=self.context)
# Test: new partner created
pids = self.res_partner.search(cr, uid, [('name', 'ilike', 'TestingPartner (copy)')]) # currently res_partner overrides default['name'] whatever its value
self.assertEqual(len(pids), 1, 'ir_actions_server: TODO')
# Test: action returned
self.assertEqual(res.get('type'), 'ir.actions.act_window', '')
# Test loops
self.assertRaises(except_orm, self.ir_actions_server.write, cr, uid, [self.act_id], {
'child_ids': [(6, 0, [self.act_id])]
})
if __name__ == '__main__':
unittest2.main()

View File

@ -270,6 +270,21 @@ class float(_column):
class date(_column):
_type = 'date'
MONTHS = [
('01', 'January'),
('02', 'February'),
('03', 'March'),
('04', 'April'),
('05', 'May'),
('06', 'June'),
('07', 'July'),
('08', 'August'),
('09', 'September'),
('10', 'October'),
('11', 'November'),
('12', 'December')
]
@staticmethod
def today(*args):
""" Returns the current date in a format fit for being a
@ -319,6 +334,22 @@ class date(_column):
class datetime(_column):
_type = 'datetime'
MONTHS = [
('01', 'January'),
('02', 'February'),
('03', 'March'),
('04', 'April'),
('05', 'May'),
('06', 'June'),
('07', 'July'),
('08', 'August'),
('09', 'September'),
('10', 'October'),
('11', 'November'),
('12', 'December')
]
@staticmethod
def now(*args):
""" Returns the current datetime in a format fit for being a

View File

@ -5114,6 +5114,40 @@ class BaseModel(object):
return False
return True
def _check_m2m_recursion(self, cr, uid, ids, field_name):
"""
Verifies that there is no loop in a hierarchical structure of records,
by following the parent relationship using the **parent** field until a loop
is detected or until a top-level record is found.
:param cr: database cursor
:param uid: current user id
:param ids: list of ids of records to check
:param field_name: field to check
:return: **True** if the operation can proceed safely, or **False** if an infinite loop is detected.
"""
field = self._all_columns.get(field_name)
field = field.column if field else None
if not field or field._type != 'many2many' or field._obj != self._name:
# field must be a many2many on itself
raise ValueError('invalid field_name: %r' % (field_name,))
query = 'SELECT distinct "%s" FROM "%s" WHERE "%s" IN %%s' % (field._id2, field._rel, field._id1)
ids_parent = ids[:]
while ids_parent:
ids_parent2 = []
for i in range(0, len(ids_parent), cr.IN_MAX):
j = i + cr.IN_MAX
sub_ids_parent = ids_parent[i:j]
cr.execute(query, (tuple(sub_ids_parent),))
ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall())))
ids_parent = ids_parent2
for i in ids_parent:
if i in ids:
return False
return True
def _get_external_ids(self, cr, uid, ids, *args, **kwargs):
"""Retrieve the External ID(s) of any database record.

View File

@ -411,14 +411,11 @@ class WorkerCron(Worker):
if rpc_request_flag:
start_time = time.time()
start_rss, start_vms = psutil.Process(os.getpid()).get_memory_info()
while True:
# acquired = openerp.addons.base.ir.ir_cron.ir_cron._acquire_job(db_name)
# TODO why isnt openerp.addons.base defined ?
import openerp.addons.base as base
acquired = base.ir.ir_cron.ir_cron._acquire_job(db_name)
if not acquired:
openerp.modules.registry.RegistryManager.delete(db_name)
break
import openerp.addons.base as base
base.ir.ir_cron.ir_cron._acquire_job(db_name)
openerp.modules.registry.RegistryManager.delete(db_name)
# dont keep cursors in multi database mode
if len(db_names) > 1:
openerp.sql_db.close_db(db_name)

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
import models
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
{
'name': 'test-workflow',
'version': '0.1',
'category': 'Tests',
'description': """A module to play with workflows.""",
'author': 'OpenERP SA',
'maintainer': 'OpenERP SA',
'website': 'http://www.openerp.com',
'depends': ['base'],
'data': ['data.xml'],
'installable': True,
'auto_install': False,
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,517 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_test_workflow_model" model="ir.ui.view">
<field name="name">Test workflow</field>
<field name="model">test.workflow.model</field>
<field name="arch" type="xml">
<form string="Test workflow">
<button name="a-b" string="a-b" type="workflow" icon="gtk-ok" colspan="1"/>
<label string="a-b"/>
<button name="trigger" string="trigger" type="object" icon="gtk-ok" colspan="1"/>
<label string="trigger"/>
</form>
</field>
</record>
<record id="action_test_workflow" model="ir.actions.act_window">
<field name="name">Test workflow</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">test.workflow.model</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem icon="STOCK_PREFERENCES" id="base.menu_tests" name="Tests" sequence="1000000"/>
<menuitem id="menu_test_workflow" parent="base.menu_tests" name="Test workflow"/>
<menuitem id="menu_test_workflow_leaf"
name="Test workflow"
action="action_test_workflow"
parent="menu_test_workflow"/>
<record id="test_workflow_trigger_1" model="test.workflow.trigger">
<!-- A single trigger record, with known ID 1 -->
</record>
<!-- A simple workflow:
a -signal-> b -trigger-> c
-->
<record id="test_workflow" model="workflow">
<field name="name">test.workflow</field>
<field name="osv">test.workflow.model</field>
<field name="on_create">True</field>
</record>
<record id="activity_a" model="workflow.activity">
<field name="wkf_id" ref="test_workflow"/>
<field name="flow_start">True</field>
<field name="name">a</field>
<field name="kind">function</field>
<field name="action">print_a()</field>
</record>
<record id="activity_b" model="workflow.activity">
<field name="wkf_id" ref="test_workflow"/>
<field name="name">b</field>
<field name="kind">function</field>
<field name="action">print_b()</field>
</record>
<record id="activity_c" model="workflow.activity">
<field name="wkf_id" ref="test_workflow"/>
<field name="flow_stop">True</field>
<field name="name">c</field>
<field name="kind">function</field>
<field name="action">print_c()</field>
</record>
<record id="trans_a_b" model="workflow.transition">
<field name="act_from" ref="activity_a"/>
<field name="act_to" ref="activity_b"/>
<field name="signal">a-b</field>
</record>
<record id="trans_b_c" model="workflow.transition">
<field name="act_from" ref="activity_b"/>
<field name="act_to" ref="activity_c"/>
<field name="condition">condition()</field>
<field name="trigger_model">test.workflow.trigger</field>
<field name="trigger_expr_id">[1]</field>
</record>
<!-- Workflow A (a single activity):
a
-->
<record id="test_workflow_a" model="workflow">
<field name="name">test.workflow.a</field>
<field name="osv">test.workflow.model.a</field>
<field name="on_create">True</field>
</record>
<record id="activity_a_a" model="workflow.activity">
<field name="wkf_id" ref="test_workflow_a"/>
<field name="flow_start">True</field>
<field name="flow_stop">True</field>
<field name="name">a</field>
<field name="kind">dummy</field>
</record>
<!-- Workflow B (a single activity):
a
The function is run when the record is created.
-->
<record id="test_workflow_b" model="workflow">
<field name="name">test.workflow.b</field>
<field name="osv">test.workflow.model.b</field>
<field name="on_create">True</field>
</record>
<record id="activity_b_a" model="workflow.activity">
<field name="wkf_id" ref="test_workflow_b"/>
<field name="flow_start">True</field>
<field name="flow_stop">True</field>
<field name="name">a</field>
<field name="kind">function</field>
<field name="action">write({'value': 1})</field>
</record>
<!-- Workflow C (a single activity):
a
The function is not run when the kind is dummy and no action_id is provided.
-->
<record id="test_workflow_c" model="workflow">
<field name="name">test.workflow.c</field>
<field name="osv">test.workflow.model.c</field>
<field name="on_create">True</field>
</record>
<record id="activity_c_a" model="workflow.activity">
<field name="wkf_id" ref="test_workflow_c"/>
<field name="flow_start">True</field>
<field name="flow_stop">True</field>
<field name="name">a</field>
<field name="kind">dummy</field>
<field name="action">write({'value': 1})</field>
</record>
<!-- Workflow D (a single activity):
a
The function is run when the kind is stopall and no action_id is provided.
-->
<record id="test_workflow_d" model="workflow">
<field name="name">test.workflow.d</field>
<field name="osv">test.workflow.model.d</field>
<field name="on_create">True</field>
</record>
<record id="activity_d_a" model="workflow.activity">
<field name="wkf_id" ref="test_workflow_d"/>
<field name="flow_start">True</field>
<field name="flow_stop">True</field>
<field name="name">a</field>
<field name="kind">stopall</field>
<field name="action">write({'value': 1})</field>
</record>
<!-- Workflow E:
a -True-> b
Both activities are run when the workflow is instanciated.
-->
<record id="test_workflow_e" model="workflow">
<field name="name">test.workflow.e</field>
<field name="osv">test.workflow.model.e</field>
<field name="on_create">True</field>
</record>
<record id="activity_e_a" model="workflow.activity">
<field name="wkf_id" ref="test_workflow_e"/>
<field name="flow_start">True</field>
<field name="name">a</field>
<field name="kind">function</field>
<field name="action">write({'value': 1})</field>
</record>
<record id="activity_e_b" model="workflow.activity">
<field name="wkf_id" ref="test_workflow_e"/>
<field name="flow_stop">True</field>
<field name="name">b</field>
<field name="kind">function</field>
<field name="action">write({'value': 2})</field>
</record>
<record id="trans_e_a_b" model="workflow.transition">
<field name="act_from" ref="activity_e_a"/>
<field name="act_to" ref="activity_e_b"/>
</record>
<!-- Workflow F:
a -signal-> b
Same as E but with a signal on the transition.
-->
<record id="test_workflow_f" model="workflow">
<field name="name">test.workflow.f</field>
<field name="osv">test.workflow.model.f</field>
<field name="on_create">True</field>
</record>
<record id="activity_f_a" model="workflow.activity">
<field name="wkf_id" ref="test_workflow_f"/>
<field name="flow_start">True</field>
<field name="name">a</field>
<field name="kind">function</field>
<field name="action">write({'value': 1})</field>
</record>
<record id="activity_f_b" model="workflow.activity">
<field name="wkf_id" ref="test_workflow_f"/>
<field name="flow_stop">True</field>
<field name="name">b</field>
<field name="kind">function</field>
<field name="action">write({'value': 2})</field>
</record>
<record id="trans_f_a_b" model="workflow.transition">
<field name="act_from" ref="activity_f_a"/>
<field name="act_to" ref="activity_f_b"/>
<field name="signal">a-b</field>
</record>
<!-- Workflow G:
a -False-> b
-->
<record id="test_workflow_g" model="workflow">
<field name="name">test.workflow.g</field>
<field name="osv">test.workflow.model.g</field>
<field name="on_create">True</field>
</record>
<record id="activity_g_a" model="workflow.activity">
<field name="wkf_id" ref="test_workflow_g"/>
<field name="flow_start">True</field>
<field name="name">a</field>
<field name="kind">function</field>
<field name="action">write({'value': 1})</field>
</record>
<record id="activity_g_b" model="workflow.activity">
<field name="wkf_id" ref="test_workflow_g"/>
<field name="flow_stop">True</field>
<field name="name">b</field>
<field name="kind">function</field>
<field name="action">write({'value': 2})</field>
</record>
<record id="trans_g_a_b" model="workflow.transition">
<field name="act_from" ref="activity_g_a"/>
<field name="act_to" ref="activity_g_b"/>
<field name="condition">False</field>
</record>
<!-- Workflow H:
a or -> b { value: 2 }
`-> c { value: 2 }
Whether the action of b or c is exectued last is non-deterministic.
-->
<record id="test_workflow_h" model="workflow">
<field name="name">test.workflow.h</field>
<field name="osv">test.workflow.model.h</field>
<field name="on_create">True</field>
</record>
<record id="activity_h_a" model="workflow.activity">
<field name="wkf_id" ref="test_workflow_h"/>
<field name="flow_start">True</field>
<field name="name">a</field>
<field name="kind">function</field>
<field name="action">write({'value': 1})</field>
<field name="split_mode">OR</field>
</record>
<record id="activity_h_b" model="workflow.activity">
<field name="wkf_id" ref="test_workflow_h"/>
<field name="flow_stop">True</field>
<field name="name">b</field>
<field name="kind">function</field>
<field name="action">write({'value': 2})</field>
</record>
<record id="activity_h_c" model="workflow.activity">
<field name="wkf_id" ref="test_workflow_h"/>
<field name="flow_stop">True</field>
<field name="name">c</field>
<field name="kind">function</field>
<field name="action">write({'value': 2})</field>
</record>
<record id="trans_h_a_b" model="workflow.transition">
<field name="act_from" ref="activity_h_a"/>
<field name="act_to" ref="activity_h_b"/>
</record>
<record id="trans_h_a_c" model="workflow.transition">
<field name="act_from" ref="activity_h_a"/>
<field name="act_to" ref="activity_h_c"/>
</record>
<!-- Workflow I:
a or -> b { value: 2 }
`false> c { value: 3 }
-->
<record id="test_workflow_i" model="workflow">
<field name="name">test.workflow.i</field>
<field name="osv">test.workflow.model.i</field>
<field name="on_create">True</field>
</record>
<record id="activity_i_a" model="workflow.activity">
<field name="wkf_id" ref="test_workflow_i"/>
<field name="flow_start">True</field>
<field name="name">a</field>
<field name="kind">function</field>
<field name="action">write({'value': 1})</field>
<field name="split_mode">OR</field>
</record>
<record id="activity_i_b" model="workflow.activity">
<field name="wkf_id" ref="test_workflow_i"/>
<field name="flow_stop">True</field>
<field name="name">b</field>
<field name="kind">function</field>
<field name="action">write({'value': 2})</field>
</record>
<record id="activity_i_c" model="workflow.activity">
<field name="wkf_id" ref="test_workflow_i"/>
<field name="flow_stop">True</field>
<field name="name">c</field>
<field name="kind">function</field>
<field name="action">write({'value': 3})</field>
</record>
<record id="trans_i_a_b" model="workflow.transition">
<field name="act_from" ref="activity_i_a"/>
<field name="act_to" ref="activity_i_b"/>
</record>
<record id="trans_i_a_c" model="workflow.transition">
<field name="act_from" ref="activity_i_a"/>
<field name="act_to" ref="activity_i_c"/>
<field name="condition">False</field>
</record>
<!-- Workflow J:
a and -> b { value: 2 }
`False> c { value: 3 }
This will stay in a because all transitions should be True at the same time.
-->
<record id="test_workflow_j" model="workflow">
<field name="name">test.workflow.j</field>
<field name="osv">test.workflow.model.j</field>
<field name="on_create">True</field>
</record>
<record id="activity_j_a" model="workflow.activity">
<field name="wkf_id" ref="test_workflow_j"/>
<field name="flow_start">True</field>
<field name="name">a</field>
<field name="kind">function</field>
<field name="action">write({'value': 1})</field>
<field name="split_mode">AND</field>
</record>
<record id="activity_j_b" model="workflow.activity">
<field name="wkf_id" ref="test_workflow_j"/>
<field name="flow_stop">True</field>
<field name="name">b</field>
<field name="kind">function</field>
<field name="action">write({'value': 2})</field>
</record>
<record id="activity_j_c" model="workflow.activity">
<field name="wkf_id" ref="test_workflow_j"/>
<field name="flow_stop">True</field>
<field name="name">c</field>
<field name="kind">function</field>
<field name="action">write({'value': 3})</field>
</record>
<record id="trans_j_a_b" model="workflow.transition">
<field name="act_from" ref="activity_j_a"/>
<field name="act_to" ref="activity_j_b"/>
</record>
<record id="trans_j_a_c" model="workflow.transition">
<field name="act_from" ref="activity_j_a"/>
<field name="act_to" ref="activity_j_c"/>
<field name="condition">False</field>
</record>
<!-- Workflow K:
a xor -> b { value: 2 }
`> c { value: 2 }
Only one (truish) transition is taken with a XOR.
-->
<record id="test_workflow_k" model="workflow">
<field name="name">test.workflow.k</field>
<field name="osv">test.workflow.model.k</field>
<field name="on_create">True</field>
</record>
<record id="activity_k_a" model="workflow.activity">
<field name="wkf_id" ref="test_workflow_k"/>
<field name="flow_start">True</field>
<field name="name">a</field>
<field name="kind">function</field>
<field name="action">write({'value': 1})</field>
<field name="split_mode">XOR</field>
</record>
<record id="activity_k_b" model="workflow.activity">
<field name="wkf_id" ref="test_workflow_k"/>
<field name="flow_stop">True</field>
<field name="name">b</field>
<field name="kind">function</field>
<field name="action">write({'value': 2})</field>
</record>
<record id="activity_k_c" model="workflow.activity">
<field name="wkf_id" ref="test_workflow_k"/>
<field name="flow_stop">True</field>
<field name="name">c</field>
<field name="kind">function</field>
<field name="action">write({'value': 2})</field>
</record>
<record id="trans_k_a_b" model="workflow.transition">
<field name="act_from" ref="activity_k_a"/>
<field name="act_to" ref="activity_k_b"/>
</record>
<record id="trans_k_a_c" model="workflow.transition">
<field name="act_from" ref="activity_k_a"/>
<field name="act_to" ref="activity_k_c"/>
</record>
<!-- Workflow L:
a -> xor c { value: 3 }
b ´
a -> and d { value: 3 }
b ´
c is run for each incoming (and taken) transition.
d is run once when all its incoming transitions are taken at the same time.
-->
<record id="test_workflow_l" model="workflow">
<field name="name">test.workflow.l</field>
<field name="osv">test.workflow.model.l</field>
<field name="on_create">True</field>
</record>
<record id="activity_l_a" model="workflow.activity">
<field name="wkf_id" ref="test_workflow_l"/>
<field name="flow_start">True</field>
<field name="name">a</field>
<field name="kind">function</field>
<field name="action">write({'value': 1})</field>
<field name="split_mode">OR</field>
</record>
<record id="activity_l_b" model="workflow.activity">
<field name="wkf_id" ref="test_workflow_l"/>
<field name="flow_start">True</field>
<field name="name">b</field>
<field name="kind">function</field>
<field name="action">write({'value': 2})</field>
<field name="split_mode">OR</field>
</record>
<record id="activity_l_c" model="workflow.activity">
<field name="wkf_id" ref="test_workflow_l"/>
<field name="flow_stop">True</field>
<field name="name">c</field>
<field name="kind">function</field>
<field name="action">write({'value': 3})</field>
<field name="join_mode">XOR</field>
</record>
<record id="activity_l_d" model="workflow.activity">
<field name="wkf_id" ref="test_workflow_l"/>
<field name="flow_stop">True</field>
<field name="name">d</field>
<field name="kind">function</field>
<field name="action">write({'value': 3})</field>
<field name="join_mode">AND</field>
</record>
<record id="trans_l_a_c" model="workflow.transition">
<field name="act_from" ref="activity_l_a"/>
<field name="act_to" ref="activity_l_c"/>
</record>
<record id="trans_l_b_c" model="workflow.transition">
<field name="act_from" ref="activity_l_b"/>
<field name="act_to" ref="activity_l_c"/>
</record>
<record id="trans_l_a_d" model="workflow.transition">
<field name="act_from" ref="activity_l_a"/>
<field name="act_to" ref="activity_l_d"/>
</record>
<record id="trans_l_b_d" model="workflow.transition">
<field name="act_from" ref="activity_l_b"/>
<field name="act_to" ref="activity_l_d"/>
</record>
</data>
</openerp>

View File

@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
import openerp
class m(openerp.osv.orm.Model):
""" A model for which we will define a workflow (see data.xml). """
_name = 'test.workflow.model'
def print_(self, cr, uid, ids, s, context=None):
print ' Running activity `%s` for record %s' % (s, ids)
return True
def print_a(self, cr, uid, ids, context=None):
return self.print_(cr, uid, ids, 'a', context)
def print_b(self, cr, uid, ids, context=None):
return self.print_(cr, uid, ids, 'b', context)
def print_c(self, cr, uid, ids, context=None):
return self.print_(cr, uid, ids, 'c', context)
def condition(self, cr, uid, ids, context=None):
m = self.pool['test.workflow.trigger']
for r in m.browse(cr, uid, [1], context=context):
if not r.value:
return False
return True
def trigger(self, cr, uid, context=None):
return openerp.workflow.trg_trigger(uid, 'test.workflow.trigger', 1, cr)
class n(openerp.osv.orm.Model):
""" A model used for the trigger feature. """
_name = 'test.workflow.trigger'
_columns = { 'value': openerp.osv.fields.boolean('Value') }
_defaults = { 'value': False }
class a(openerp.osv.orm.Model):
_name = 'test.workflow.model.a'
_columns = { 'value': openerp.osv.fields.integer('Value') }
_defaults = { 'value': 0 }
class b(openerp.osv.orm.Model):
_name = 'test.workflow.model.b'
_inherit = 'test.workflow.model.a'
class c(openerp.osv.orm.Model):
_name = 'test.workflow.model.c'
_inherit = 'test.workflow.model.a'
class d(openerp.osv.orm.Model):
_name = 'test.workflow.model.d'
_inherit = 'test.workflow.model.a'
class e(openerp.osv.orm.Model):
_name = 'test.workflow.model.e'
_inherit = 'test.workflow.model.a'
for name in 'bcdefghijkl':
type(
name,
(openerp.osv.orm.Model,),
{
'_name': 'test.workflow.model.%s' % name,
'_inherit': 'test.workflow.model.a',
})
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
from . import test_workflow
fast_suite = [
]
checks = [
test_workflow,
]
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,183 @@
# -*- coding: utf-8 -*-
import openerp
from openerp import SUPERUSER_ID
from openerp.tests import common
class test_workflows(common.TransactionCase):
def check_activities(self, model_name, i, names):
""" Check that the record i has workitems in the given activity names.
"""
instance = self.registry('workflow.instance')
workitem = self.registry('workflow.workitem')
# Given the workflow instance associated to the record ...
instance_id = instance.search(
self.cr, SUPERUSER_ID,
[('res_type', '=', model_name), ('res_id', '=', i)])
self.assertTrue( instance_id, 'A workflow instance is expected.')
# ... get all its workitems ...
workitem_ids = workitem.search(
self.cr, SUPERUSER_ID,
[('inst_id', '=', instance_id[0])])
self.assertTrue(
workitem_ids,
'The workflow instance should have workitems.')
# ... and check the activity the are in against the provided names.
workitem_records = workitem.browse(
self.cr, SUPERUSER_ID, workitem_ids)
self.assertEqual(
sorted([item.act_id.name for item in workitem_records]),
sorted(names))
def check_value(self, model_name, i, value):
""" Check that the record i has the given value.
"""
model = self.registry(model_name)
record = model.read(self.cr, SUPERUSER_ID, [i], ['value'])[0]
self.assertEqual(record['value'], value)
def test_workflow(self):
model = self.registry('test.workflow.model')
trigger = self.registry('test.workflow.trigger')
i = model.create(self.cr, SUPERUSER_ID, {})
self.check_activities(model._name, i, ['a'])
# a -> b is just a signal.
model.signal_workflow(self.cr, SUPERUSER_ID, [i], 'a-b')
self.check_activities(model._name, i, ['b'])
# b -> c is a trigger (which is False),
# so we remain in the b activity.
model.trigger(self.cr, SUPERUSER_ID, [i])
self.check_activities(model._name, i, ['b'])
# b -> c is a trigger (which is set to True).
# so we go in c when the trigger is called.
trigger.write(self.cr, SUPERUSER_ID, [1], {'value': True})
model.trigger(self.cr, SUPERUSER_ID)
self.check_activities(model._name, i, ['c'])
self.assertEqual(
True,
True)
model.unlink(self.cr, SUPERUSER_ID, [i])
def test_workflow_a(self):
model = self.registry('test.workflow.model.a')
i = model.create(self.cr, SUPERUSER_ID, {})
self.check_activities(model._name, i, ['a'])
self.check_value(model._name, i, 0)
model.unlink(self.cr, SUPERUSER_ID, [i])
def test_workflow_b(self):
model = self.registry('test.workflow.model.b')
i = model.create(self.cr, SUPERUSER_ID, {})
self.check_activities(model._name, i, ['a'])
self.check_value(model._name, i, 1)
model.unlink(self.cr, SUPERUSER_ID, [i])
def test_workflow_c(self):
model = self.registry('test.workflow.model.c')
i = model.create(self.cr, SUPERUSER_ID, {})
self.check_activities(model._name, i, ['a'])
self.check_value(model._name, i, 0)
model.unlink(self.cr, SUPERUSER_ID, [i])
def test_workflow_d(self):
model = self.registry('test.workflow.model.d')
i = model.create(self.cr, SUPERUSER_ID, {})
self.check_activities(model._name, i, ['a'])
self.check_value(model._name, i, 1)
model.unlink(self.cr, SUPERUSER_ID, [i])
def test_workflow_e(self):
model = self.registry('test.workflow.model.e')
i = model.create(self.cr, SUPERUSER_ID, {})
self.check_activities(model._name, i, ['b'])
self.check_value(model._name, i, 2)
model.unlink(self.cr, SUPERUSER_ID, [i])
def test_workflow_f(self):
model = self.registry('test.workflow.model.f')
i = model.create(self.cr, SUPERUSER_ID, {})
self.check_activities(model._name, i, ['a'])
self.check_value(model._name, i, 1)
model.signal_workflow(self.cr, SUPERUSER_ID, [i], 'a-b')
self.check_activities(model._name, i, ['b'])
self.check_value(model._name, i, 2)
model.unlink(self.cr, SUPERUSER_ID, [i])
def test_workflow_g(self):
model = self.registry('test.workflow.model.g')
i = model.create(self.cr, SUPERUSER_ID, {})
self.check_activities(model._name, i, ['a'])
self.check_value(model._name, i, 1)
model.unlink(self.cr, SUPERUSER_ID, [i])
def test_workflow_h(self):
model = self.registry('test.workflow.model.h')
i = model.create(self.cr, SUPERUSER_ID, {})
self.check_activities(model._name, i, ['b', 'c'])
self.check_value(model._name, i, 2)
model.unlink(self.cr, SUPERUSER_ID, [i])
def test_workflow_i(self):
model = self.registry('test.workflow.model.i')
i = model.create(self.cr, SUPERUSER_ID, {})
self.check_activities(model._name, i, ['b'])
self.check_value(model._name, i, 2)
model.unlink(self.cr, SUPERUSER_ID, [i])
def test_workflow_j(self):
model = self.registry('test.workflow.model.j')
i = model.create(self.cr, SUPERUSER_ID, {})
self.check_activities(model._name, i, ['a'])
self.check_value(model._name, i, 1)
model.unlink(self.cr, SUPERUSER_ID, [i])
def test_workflow_k(self):
model = self.registry('test.workflow.model.k')
i = model.create(self.cr, SUPERUSER_ID, {})
# Non-determinisitic: can be b or c
# self.check_activities(model._name, i, ['b'])
# self.check_activities(model._name, i, ['c'])
self.check_value(model._name, i, 2)
model.unlink(self.cr, SUPERUSER_ID, [i])
def test_workflow_l(self):
model = self.registry('test.workflow.model.l')
i = model.create(self.cr, SUPERUSER_ID, {})
self.check_activities(model._name, i, ['c', 'c', 'd'])
self.check_value(model._name, i, 3)
model.unlink(self.cr, SUPERUSER_ID, [i])

View File

@ -272,18 +272,6 @@ def reverse_enumerate(l):
"""
return izip(xrange(len(l)-1, -1, -1), reversed(l))
#----------------------------------------------------------
# SMS
#----------------------------------------------------------
# text must be latin-1 encoded
def sms_send(user, password, api_id, text, to):
import urllib
url = "http://api.urlsms.com/SendSMS.aspx"
#url = "http://196.7.150.220/http/sendmsg"
params = urllib.urlencode({'UserID': user, 'Password': password, 'SenderID': api_id, 'MsgText': text, 'RecipientMobileNo':to})
urllib.urlopen(url+"?"+params)
# FIXME: Use the logger if there is an error
return True
class UpdateableStr(local):
""" Class that stores an updateable string (used in wizards)

View File

@ -19,62 +19,92 @@
#
##############################################################################
"""
Evaluate workflow code found in activity actions and transition conditions.
"""
import openerp
from openerp.tools.safe_eval import safe_eval as eval
class Env(dict):
def __init__(self, cr, uid, model, ids):
"""
Dictionary class used as an environment to evaluate workflow code (such as
the condition on transitions).
This environment provides sybmols for cr, uid, id, model name, model
instance, column names, and all the record (the one obtained by browsing
the provided ID) attributes.
"""
def __init__(self, cr, uid, model, id):
self.cr = cr
self.uid = uid
self.model = model
self.ids = ids
self.id = id
self.ids = [id]
self.obj = openerp.registry(cr.dbname)[model]
self.columns = self.obj._columns.keys() + self.obj._inherit_fields.keys()
def __getitem__(self, key):
if (key in self.columns) or (key in dir(self.obj)):
res = self.obj.browse(self.cr, self.uid, self.ids[0])
res = self.obj.browse(self.cr, self.uid, self.id)
return res[key]
else:
return super(Env, self).__getitem__(key)
def _eval_expr(cr, ident, workitem, action):
ret=False
assert action, 'You used a NULL action in a workflow, use dummy node instead.'
for line in action.split('\n'):
def _eval_expr(cr, ident, workitem, lines):
"""
Evaluate each line of ``lines`` with the ``Env`` environment, returning
the value of the last line.
"""
assert lines, 'You used a NULL action in a workflow, use dummy node instead.'
uid, model, id = ident
result = False
for line in lines.split('\n'):
line = line.strip()
if not line:
continue
uid=ident[0]
model=ident[1]
ids=[ident[2]]
if line =='True':
ret=True
elif line =='False':
ret=False
if line == 'True':
result = True
elif line == 'False':
result = False
else:
env = Env(cr, uid, model, ids)
ret = eval(line, env, nocopy=True)
return ret
env = Env(cr, uid, model, id)
result = eval(line, env, nocopy=True)
return result
def execute_action(cr, ident, workitem, activity):
obj = openerp.registry(cr.dbname)['ir.actions.server']
ctx = {'active_model':ident[1], 'active_id':ident[2], 'active_ids':[ident[2]]}
result = obj.run(cr, ident[0], [activity['action_id']], ctx)
"""
Evaluate the ir.actions.server action specified in the activity.
"""
uid, model, id = ident
ir_actions_server = openerp.registry(cr.dbname)['ir.actions.server']
context = { 'active_model': model, 'active_id': id, 'active_ids': [id] }
result = ir_actions_server.run(cr, uid, [activity['action_id']], context)
return result
def execute(cr, ident, workitem, activity):
"""
Evaluate the action specified in the activity.
"""
return _eval_expr(cr, ident, workitem, activity['action'])
def check(cr, workitem, ident, transition, signal):
"""
Test if a transition can be taken. The transition can be taken if:
- the signal name matches,
- the uid is SUPERUSER_ID or the user groups contains the transition's
group,
- the condition evaluates to a truish value.
"""
if transition['signal'] and signal != transition['signal']:
return False
uid = ident[0]
if transition['group_id'] and uid != 1:
if uid != openerp.SUPERUSER_ID and transition['groups_id']:
registry = openerp.registry(cr.dbname)
user_groups = registry['res.users'].read(cr, uid, [uid], ['groups_id'])[0]['groups_id']
if not transition['group_id'] in user_groups:
if transition['group_id'] not in user_groups:
return False
return _eval_expr(cr, ident, workitem, transition['condition'])

View File

@ -19,9 +19,10 @@ from . import scaffold
from . import uninstall
from . import update
from . import web
from . import grunt_tests
command_list_server = (conf, cron, drop, initialize, model, module, read, run_tests,
scaffold, uninstall, update, web, )
scaffold, uninstall, update, web, grunt_tests, )
command_list_client = (Call, Open, Show, ConsumeNothing, ConsumeMemory,
LeakMemory, ConsumeCPU, Bench, BenchRead,

View File

@ -0,0 +1,50 @@
"""
Search for Gruntfile.js files in all the addons and launch them using the 'grunt test' command.
"""
import common
import fnmatch
import os
import re
import sys
import subprocess
def grunt_tester(directories, log = sys.stdout):
result = 0
matches = []
for direc in directories:
for root, dirnames, filenames in os.walk(direc):
for filename in fnmatch.filter(filenames, 'Gruntfile.js'):
full = os.path.join(root, filename)
if re.match(r"(^.*?/node_modules/.*$)|(^.*?/lib/.*$)", full):
continue
matches.append(full)
for file_ in matches:
folder = os.path.dirname(file_)
p = subprocess.Popen(['npm', 'install'], cwd=folder)
if p.wait() != 0:
raise Exception("Failed to install dependencies for Gruntfile located in folder %s" % folder)
p = subprocess.Popen(['grunt', 'test', '--no-color'], cwd=folder, stdout=log, stderr=log)
if p.wait() != 0:
result = 1
return result
def run(args):
if args.addons:
args.addons = args.addons.split(':')
else:
args.addons = []
result = grunt_tester(args.addons)
if result != 0:
sys.exit(result)
def add_parser(subparsers):
parser = subparsers.add_parser('grunt-tests',
description='Run the tests contained in Gruntfile.js files.')
common.add_addons_argument(parser)
parser.set_defaults(run=run)