[IMP] repository cleanup
- move packaging stuff to setup - remove historical stuff - remove oe, odoo-cmd-fme will be merged with the convered commands - add an odoo.py script to run odo and boostrap it - simplify README - prepare to move documentation to the github wiki
85
README.md
|
@ -1,73 +1,48 @@
|
||||||
About Odoo
|
About Odoo
|
||||||
==========
|
==========
|
||||||
|
|
||||||
Odoo is a suite of open source Business apps. More info at http://www.odoo.com
|
Odoo is a suite of open source Business apps. More info at http://www.odoo.com
|
||||||
|
|
||||||
Installation
|
Evaluating Odoo
|
||||||
============
|
---------------
|
||||||
|
|
||||||
[Setup/migration guide for employees](https://github.com/odoo/odoo/blob/master/doc/git.rst)
|
The easiest way to test Odoo is the free trial, NO email registration is
|
||||||
|
required, select "skip this step" to skip it.
|
||||||
|
|
||||||
|
https://www.odoo.com/page/start
|
||||||
|
|
||||||
|
|
||||||
Migration from bazaar
|
Getting starting with Odoo developement
|
||||||
=====================
|
---------------------------------------
|
||||||
|
|
||||||
If you have existing bazaar branches and want to move them to a git repository,
|
If you are a developer type the following command at your terminal:
|
||||||
there are several options:
|
|
||||||
|
|
||||||
* download http://nightly.openerp.com/move-branch.zip and run it with
|
wget -O- https://raw.githubusercontent.com/odoo/odoo/master/odoo.py | python
|
||||||
`python move-branch.zip -h` (for the help). It should be able to convert
|
|
||||||
simple-enough branches for you (even if they have merge commits &al)
|
Then follow the tutorial here:
|
||||||
* Extract the branch contents as patches and use `git apply` or `git am` to
|
|
||||||
rebuild a branch from them
|
https://doc.openerp.com/trunk/server/howto/howto_website/
|
||||||
* Replay the branch by hand
|
|
||||||
|
If you are an Odoo employee type the following to add the odoo-dev remote
|
||||||
|
|
||||||
|
$ cd odoo; ./odoo.py setup_git_dev
|
||||||
|
|
||||||
|
|
||||||
System Requirements
|
Packages, tarballs and installers
|
||||||
-------------------
|
---------------------------------
|
||||||
|
|
||||||
The dependencies are listed in setup.py
|
* Debian packages
|
||||||
|
|
||||||
|
Add this apt repository to your /etc/apt/sources.list file
|
||||||
|
|
||||||
Debian/Ubuntu
|
deb http://nightly.openerp.com/8.0/deb/ ./
|
||||||
-------------
|
|
||||||
|
|
||||||
Add the apt repository
|
Then type:
|
||||||
|
|
||||||
deb http://nightly.openerp.com/7.0/deb/ ./
|
$ sudo apt-get update
|
||||||
|
$ sudo apt-get install odoo
|
||||||
|
|
||||||
in your source.list and type:
|
* Source tarballs http://nightly.openerp.com/
|
||||||
|
* Windows installer http://nightly.openerp.com/
|
||||||
$ sudo apt-get update
|
* RPM package http://nightly.openerp.com/
|
||||||
$ sudo apt-get install openerp
|
|
||||||
|
|
||||||
Or download the deb file and type:
|
|
||||||
|
|
||||||
$ sudo dpkg -i <openerp-deb-filename>
|
|
||||||
$ sudo apt-get install -f
|
|
||||||
|
|
||||||
RedHat, Fedora, CentOS
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
Install the required dependencies:
|
|
||||||
|
|
||||||
$ yum install python
|
|
||||||
$ easy_install pip
|
|
||||||
$ pip install .....
|
|
||||||
|
|
||||||
Install the openerp rpm
|
|
||||||
|
|
||||||
$ rpm -i openerp-VERSION.rpm
|
|
||||||
|
|
||||||
Windows
|
|
||||||
-------
|
|
||||||
|
|
||||||
Check the notes in setup.py
|
|
||||||
|
|
||||||
|
|
||||||
Setting up your database
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
Point your browser to http://localhost:8069/ and click "Manage Databases", the
|
|
||||||
default master password is "admin".
|
|
||||||
|
|
||||||
|
|
95
checkout.sh
|
@ -1,95 +0,0 @@
|
||||||
#/bin/sh
|
|
||||||
set -e
|
|
||||||
|
|
||||||
ODOO=https://github.com/odoo/odoo.git
|
|
||||||
DEV=https://github.com/odoo-dev/odoo.git
|
|
||||||
|
|
||||||
usage () {
|
|
||||||
cat <<EOF
|
|
||||||
Usage: $0 [-m] [COPYNAME]
|
|
||||||
|
|
||||||
Checks out and sets up the Odoo git repository for "internal" development.
|
|
||||||
|
|
||||||
* Checks out the "production" repository (production branches) as the "odoo"
|
|
||||||
remote
|
|
||||||
* Checks out the "development" repository (for employee development branches)
|
|
||||||
as the "dev" repository
|
|
||||||
|
|
||||||
By default, the working copy is "odoo"
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-h displays this help text
|
|
||||||
-m includes github's merge refs. These are the pull requests to "odoo"
|
|
||||||
which merge cleanly into the main repository, after having applied
|
|
||||||
them to said repository
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
while getopts :hm opt
|
|
||||||
do
|
|
||||||
case $opt in
|
|
||||||
h)
|
|
||||||
usage
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
m)
|
|
||||||
include_merge=yes
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
usage
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
shift $((OPTIND-1))
|
|
||||||
copyname=${1:-"odoo"}
|
|
||||||
|
|
||||||
# Collect basic configuration data, ensures correct configuration of that repo
|
|
||||||
printf "Enter your full name: "
|
|
||||||
read name
|
|
||||||
printf "Enter your (work) email: "
|
|
||||||
read email
|
|
||||||
|
|
||||||
# create & set up repo
|
|
||||||
git init $copyname
|
|
||||||
cd $copyname
|
|
||||||
|
|
||||||
git config user.name "$name"
|
|
||||||
git config user.email "$email"
|
|
||||||
|
|
||||||
# pre-push script preventing push to odoo repo by default. Git just execs
|
|
||||||
# them, so they need a correct shebang and exec bit
|
|
||||||
# if things get more extensive, should probably use git init templates
|
|
||||||
cat <<EOF > .git/hooks/pre-push
|
|
||||||
#!/bin/sh
|
|
||||||
remote="\$1"
|
|
||||||
url="\$2"
|
|
||||||
|
|
||||||
if [ "\$url" != "$ODOO" ]
|
|
||||||
then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Pushing to the odoo remote ($ODOO) is forbidden, push to the dev remote"
|
|
||||||
echo
|
|
||||||
echo "See git help push if you really want to push to odoo"
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
EOF
|
|
||||||
chmod +x .git/hooks/pre-push
|
|
||||||
|
|
||||||
# add basic repos as remotes
|
|
||||||
git remote add odoo $ODOO
|
|
||||||
git remote add dev $DEV
|
|
||||||
|
|
||||||
if [ $include_merge ]
|
|
||||||
then
|
|
||||||
git remote add merge $ODOO
|
|
||||||
git config remote.merge.fetch '+refs/pull/*/merge:refs/remotes/merge/*'
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo
|
|
||||||
git remote update
|
|
||||||
|
|
||||||
exit 0
|
|
|
@ -1,34 +0,0 @@
|
||||||
.. _adding-command:
|
|
||||||
|
|
||||||
Adding a new command
|
|
||||||
====================
|
|
||||||
|
|
||||||
``oe`` uses the argparse_ library to implement commands. Each
|
|
||||||
command lives in its own ``openerpcommand/<command>.py`` file.
|
|
||||||
|
|
||||||
.. _argparse: http://docs.python.org/2.7/library/argparse.html
|
|
||||||
|
|
||||||
To create a new command, probably the most simple way to get started is to
|
|
||||||
copy/paste an existing command, say ``openerpcommand/initialize.py`` to
|
|
||||||
``openerpcommand/foo.py``. In the newly created file, the important bits
|
|
||||||
are the ``run(args)`` and ``add_parser(subparsers)`` functions.
|
|
||||||
|
|
||||||
``add_parser``'s responsability is to create a (sub-)parser for the command,
|
|
||||||
i.e. describe the different options and flags. The last thing it does is to set
|
|
||||||
``run`` as the function to call when the command is invoked.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
> def add_parser(subparsers):
|
|
||||||
> parser = subparsers.add_parser('<command-name>',
|
|
||||||
> description='...')
|
|
||||||
> parser.add_argument(...)
|
|
||||||
> ...
|
|
||||||
> parser.set_defaults(run=run)
|
|
||||||
|
|
||||||
``run(args)`` actually implements the command. It should be kept as simple as
|
|
||||||
possible and delegate most of its work to small functions (probably placed at
|
|
||||||
the top of the new file). In other words, its responsability is mainly to
|
|
||||||
deal with the presence/absence/pre-processing of ``argparse``'s arguments.
|
|
||||||
|
|
||||||
Finally, the module must be added to ``openerpcommand/__init__.py``.
|
|
|
@ -1,40 +0,0 @@
|
||||||
.. _commands:
|
|
||||||
|
|
||||||
Available commands
|
|
||||||
==================
|
|
||||||
|
|
||||||
This page explain some of the available ``oe`` commands. For an overview about
|
|
||||||
``oe``, see :doc:`openerp-command`.
|
|
||||||
|
|
||||||
Keep in mind that ``oe --help`` and ``oe <command> --help`` already give a lot
|
|
||||||
of information about the commands and their options and flags.
|
|
||||||
|
|
||||||
``web``
|
|
||||||
-------
|
|
||||||
|
|
||||||
The ``web`` command is used to create a single OpenERP server process to handle
|
|
||||||
regular HTTP requests and XML-RPC requests. It is possible to execute such
|
|
||||||
process multiple times, possibly on different machines.
|
|
||||||
|
|
||||||
It is possible to chose the ``--threaded`` or ``--gevent`` flags. It is
|
|
||||||
recommanded to use ``--threaded`` only when running a single process.
|
|
||||||
``--gevent`` is experimental; it is planned to use it for the embedded chat
|
|
||||||
feature.
|
|
||||||
|
|
||||||
Example invocation::
|
|
||||||
|
|
||||||
> oe web --addons ../../addons/trunk:../../web/trunk/addons --threaded
|
|
||||||
|
|
||||||
``cron``
|
|
||||||
--------
|
|
||||||
|
|
||||||
The ``cron`` command is used to create a single OpenERP process to execute
|
|
||||||
so-called cron jobs, also called scheduled tasks in the OpenERP interface. As
|
|
||||||
for the ``web`` command, multiple cron processes can be run side by side.
|
|
||||||
|
|
||||||
It is necessary to specify on the command-line which database need to be
|
|
||||||
watched by the cron process with the ``--database`` option.
|
|
||||||
|
|
||||||
Example invocation::
|
|
||||||
|
|
||||||
> oe cron --addons ../../addons/trunk:../../web/trunk/addons --database production
|
|
|
@ -30,16 +30,6 @@ OpenERP Server
|
||||||
form-view-guidelines
|
form-view-guidelines
|
||||||
ir_actions
|
ir_actions
|
||||||
|
|
||||||
OpenERP Command
|
|
||||||
'''''''''''''''
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
openerp-command.rst
|
|
||||||
commands.rst
|
|
||||||
adding-command.rst
|
|
||||||
|
|
||||||
OpenERP Server API
|
OpenERP Server API
|
||||||
''''''''''''''''''
|
''''''''''''''''''
|
||||||
|
|
||||||
|
|
|
@ -1,66 +0,0 @@
|
||||||
.. _openerp-command:
|
|
||||||
|
|
||||||
The ``oe`` script
|
|
||||||
=================
|
|
||||||
|
|
||||||
The ``oe`` script provides a set of command-line tools around the OpenERP
|
|
||||||
framework. It is meant to replace the older ``openerp-server`` script (which
|
|
||||||
is still available).
|
|
||||||
|
|
||||||
Using ``oe``
|
|
||||||
------------
|
|
||||||
|
|
||||||
In contrast to the previous ``openerp-server`` script, ``oe`` defines a few
|
|
||||||
commands, each with its own set of flags and options. You can get some
|
|
||||||
information for any of them with
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
> oe <command> --help
|
|
||||||
|
|
||||||
For instance::
|
|
||||||
|
|
||||||
> oe run-tests --help
|
|
||||||
|
|
||||||
Some ``oe`` options can be provided via environment variables. For instance::
|
|
||||||
|
|
||||||
> export OPENERP_DATABASE=trunk
|
|
||||||
> export OPENERP_HOST=127.0.0.1
|
|
||||||
> export OPENERP_PORT=8069
|
|
||||||
|
|
||||||
Depending on your needs, you can group all of the above in one single script;
|
|
||||||
for instance here is a, say, ``test-trunk-view-validation.sh`` file::
|
|
||||||
|
|
||||||
COMMAND_REPO=/home/thu/repos/command/trunk/
|
|
||||||
SERVER_REPO=/home/thu/repos/server/trunk
|
|
||||||
|
|
||||||
export PYTHONPATH=$SERVER_REPO:$COMMAND_REPO
|
|
||||||
export PATH=$SERVER_REPO:$COMMAND_REPO:$PATH
|
|
||||||
export OPENERP_DATABASE=trunk
|
|
||||||
export OPENERP_HOST=127.0.0.1
|
|
||||||
export OPENERP_PORT=8069
|
|
||||||
|
|
||||||
# The -d ignored is actually needed by `oe` even though `test_view_validation`
|
|
||||||
# itself does not need it.
|
|
||||||
oe run-tests -d ignored -m openerp.test_view_validation
|
|
||||||
|
|
||||||
Available commands
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
See the :doc:`commands` page.
|
|
||||||
|
|
||||||
Adding new commands
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
See the :doc:`adding-command` page.
|
|
||||||
|
|
||||||
Bash completion
|
|
||||||
---------------
|
|
||||||
|
|
||||||
A preliminary ``oe-bash-completion`` file is provided. After sourcing it,
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
> . oe-bash-completion
|
|
||||||
|
|
||||||
completion (using the TAB character) in Bash should work.
|
|
1822
history/Changelog
|
@ -1,951 +0,0 @@
|
||||||
4.2.1
|
|
||||||
Bugfixes
|
|
||||||
Fix context for source_count function
|
|
||||||
Create stock move on production for products without BOM lines
|
|
||||||
Add IBAN fields in bank view
|
|
||||||
Fix uninitialize variable in import data
|
|
||||||
Update due date on invoice when payment term change
|
|
||||||
Fix store on field function that have type many2one or one2one
|
|
||||||
Request summary must be truncate
|
|
||||||
Partner event name must be truncate
|
|
||||||
Remove parent field on partner contact view
|
|
||||||
Fix icon type on journal period
|
|
||||||
Remove exception on the size of char field
|
|
||||||
Fix reference on move line that comes from invoice (Customer != Supplier)
|
|
||||||
Add function search on sheet_id of timesheet_sheet
|
|
||||||
Don't return 0 for balance account if there is no fiscal year
|
|
||||||
Fix set to draft for expense, now really set to draft
|
|
||||||
Add product and partner in the recursive call of tax compute
|
|
||||||
Don't compute balance account for inactive account
|
|
||||||
Fix bad encoding in log message on report_sxw
|
|
||||||
Fix overdue report for refund lines
|
|
||||||
Don't start server in non secure mode if secure mode have been set
|
|
||||||
Fix default value of move line if move_id is not find
|
|
||||||
Fix _product_partner_ref for cannot concatenate 'str' and 'bool' objects
|
|
||||||
Add partner_id in the context of SO for browsing the product
|
|
||||||
Fix multi-tax code on invoice
|
|
||||||
Fix tax definition for Belgium chart
|
|
||||||
Remove compute debit/credit on inactive account
|
|
||||||
Fix the way the tax are rounded for invoice with tax included prices
|
|
||||||
Fix SO to use the right uom and price to create invoice
|
|
||||||
Fix on_chnage uos on SO to return the id not the browse record
|
|
||||||
Add condition on the button "Sending goods>Packing to be invoiced" to show
|
|
||||||
only customer packings
|
|
||||||
Fix zero division error when the quantity is zero on an invoice line
|
|
||||||
Fix duplicate timesheet line that have been invoiced
|
|
||||||
Fix invoice report for bad removeParentNode tag
|
|
||||||
Fix priority for product view
|
|
||||||
Fix tax line computation when encoding account lines manually
|
|
||||||
Fix refund supplier invoice to have the same journal
|
|
||||||
New chinese translation
|
|
||||||
Pass context to action_done on stock move
|
|
||||||
Add product_uom change on sale order line
|
|
||||||
Fix demo data for working time UOM
|
|
||||||
Fix _sheet function in timesheet_sheet when called with a list of non
|
|
||||||
unique id
|
|
||||||
Remove commit inside function validate on account move
|
|
||||||
Use one function to post account move
|
|
||||||
Fix computation of sale/purchase amount in segmentation module
|
|
||||||
Use standar uom converion in analytic lines
|
|
||||||
Add journal_id in context for account move line search in payment module
|
|
||||||
Fix wrong id used by pricelist based on partner form
|
|
||||||
Use partner reference from SO/PO for invoice name if there is one
|
|
||||||
Make analysis analytic module include child accounts
|
|
||||||
|
|
||||||
4.2.0
|
|
||||||
Summary:
|
|
||||||
Add new view graph
|
|
||||||
REPORT_INTRASTAT: new module
|
|
||||||
KERNEL: add netrpc (speed improvement)
|
|
||||||
REPORT_STOCK: add report on stock by stock location and production lots
|
|
||||||
HR_TIMESHEET_INVOICE: add final invoice
|
|
||||||
MULTI_COMPANY_ACCOUNT: new module
|
|
||||||
ADD modules publication tools
|
|
||||||
KERNEL: add timezone
|
|
||||||
KERNEL: add concurnecy check
|
|
||||||
BASE: allow to specify many view_id in act_window
|
|
||||||
BASE: add ir.rules (acces base on record fields)
|
|
||||||
KERNEL: add search_count on objects
|
|
||||||
KERNEL: add assert tools (unit test)
|
|
||||||
KERNEL: improve workflow speed
|
|
||||||
KERNEL: move some modules to extra_addons
|
|
||||||
Bugfixes:
|
|
||||||
Fix pooler for multi-db
|
|
||||||
REPORT_ANALYTIC: new reports
|
|
||||||
BOARD_ACCOUNT: new dashboard for accountants
|
|
||||||
PURCHASE: allow multiple pickings for the same purchase order
|
|
||||||
STOCK: When refunding picking: confirm & Assign the newly generated picking
|
|
||||||
PRODUCT: add average price
|
|
||||||
STOCK: Fix workflow for stock
|
|
||||||
TOOLS: Fix export translate for wizard
|
|
||||||
KERNEL: add id in import_data
|
|
||||||
BASE: add history rate to currency
|
|
||||||
ACCOUNT: partner_id is now required for an invoice
|
|
||||||
HR_TIMESHEET: add exception if employee haven't product
|
|
||||||
I18N: new fr_CH file
|
|
||||||
HR_EXPENSE: fix domain
|
|
||||||
ACCOUNT: Fix invoice with currency and payment term
|
|
||||||
ACCOUNT: Fix currency
|
|
||||||
KERNEL: add pidfile
|
|
||||||
ACCOUNT,PURCHASE,SALE: use partner lang for description
|
|
||||||
Model Acces: Unlink permission (delete) is now available
|
|
||||||
KERNEL: Remove set for python2.3
|
|
||||||
HR: add id to Attendance menu
|
|
||||||
PRODUCT: add dimension to packaging
|
|
||||||
ACCOUNT: new cash_discount on payment term
|
|
||||||
KERNEL: Add price accuracy
|
|
||||||
BASE: Function to remove installed modules
|
|
||||||
REPORT_SALE: fix for sale without line
|
|
||||||
PURCHASE: remove use of currency
|
|
||||||
KERNEL: fix set without values
|
|
||||||
PURCHASE: fix domain pricelist
|
|
||||||
INVOICE: use date for currency rate
|
|
||||||
KERNEL: Fix import many2many by id
|
|
||||||
KERNEL: run the cron
|
|
||||||
ACCOUNT: bank statment line now have a ref t othe corresponding invoice
|
|
||||||
ACCOUNT: Add possibilitty to include tax amount in base amount for the computation of the next taxes
|
|
||||||
ACCOUNT: Add product in tax compute python code
|
|
||||||
KERNEL: use reportlab 2.0
|
|
||||||
BASE: fix import the same lang
|
|
||||||
ACCOUNT: fix tax code
|
|
||||||
ACCOUNT: define tax account for invoice and refund
|
|
||||||
ACCOUNT: add supplier tax to product
|
|
||||||
ACCOUNT: don't overwrite tax_code on the creation for account line
|
|
||||||
PURCHASE: use partner code for report order
|
|
||||||
KERNEL: fix pooler netsvc for multi-db
|
|
||||||
TOOLS: add ref to function tag
|
|
||||||
PRODUCT: fix digits on volume and weight, add weight_net
|
|
||||||
ACCOUNT: split to new module account_cash_discount
|
|
||||||
ORM : error message on python constraints are now displayed correctly
|
|
||||||
ACCOUNT: add partner to tax compute context
|
|
||||||
KERNEL: improve logger
|
|
||||||
PROJECT: add check_recursion for project
|
|
||||||
HR_TIMESHEET_INVOICE: improve create invoice
|
|
||||||
ACCOUNT: add product_id to analytic line create by invoice
|
|
||||||
KERNEL: fix the inheritance mechanism
|
|
||||||
KERNEL: Fix use always basename for cvs file
|
|
||||||
BASE: fix IBAN len to 27
|
|
||||||
INVOICE: fix invoice number for analytic
|
|
||||||
REPORT: add replace tag for custom header
|
|
||||||
ACCOUNT: add ref to analytic line
|
|
||||||
BASE: prevent exception in ir_cron
|
|
||||||
SALE: fix uos for tax_amount
|
|
||||||
MRP: fix dbname in _procure_confirm
|
|
||||||
HR_EXPENSE: add domain to analytic_account
|
|
||||||
KERNEL: use 0 instead of False for fix on _fnct_read
|
|
||||||
SUBSCRIPTION: add required to model
|
|
||||||
HR_TIMESHEET: add rounding on report
|
|
||||||
SALE: Fix cancel invoice and recreate invoice, now cancel also the order lines
|
|
||||||
STOCK-DELIVERY: add wizard invoice_onshipping from delivery to stock
|
|
||||||
STOCK: use tax from sale for invoice
|
|
||||||
BASE: improve copy of res.partner
|
|
||||||
ACCOUNT: pay only invoice if not in state draft
|
|
||||||
REPORT: fix rml translation, translate before eval
|
|
||||||
PRODUCT_EXTENDED: don't use seller price for bom price
|
|
||||||
ACCOUNT_TAX_INCLUDE: fix right amount in account move generate with tax_include
|
|
||||||
BASE: improve workflow print
|
|
||||||
SALE: fix workflow error when create invoice from wizard
|
|
||||||
MRP: Use company currency for Product Cost Structure
|
|
||||||
BASE: prevent recursion in company
|
|
||||||
KERNEL: Fix deleted property and many2one
|
|
||||||
KERNEL: allow directory for import csv
|
|
||||||
KERNEL: add store option to fields function
|
|
||||||
ACCOUNT: use property_account_tax on on_change_product
|
|
||||||
KERNEL: add right-click for translate label
|
|
||||||
KERNEL: fix log of backtrace
|
|
||||||
KERNEL: fix search on xxx2many
|
|
||||||
BASE: use tool to call popen.pipe2
|
|
||||||
KERNEL: fix print workflow on win32
|
|
||||||
BASE: fix US states
|
|
||||||
KERNEL: use python 2.3 format_exception
|
|
||||||
ACCOUNT: add multi-company into base accounting
|
|
||||||
KERNEL: check return code for exec_pg_command_pipe
|
|
||||||
KERNEL: fix search with active args
|
|
||||||
KERNEL: improve _sql_contsraints, now insert if doesn't exist
|
|
||||||
KERNEL: remove old inheritor and add _constraints and _sql_constraints to the fields inherited
|
|
||||||
CRM: bugfix mailgate
|
|
||||||
PURCHASE: fix the UOM for purchase line and improve update price unit
|
|
||||||
ACCOUNT: new invoice view
|
|
||||||
KERNEL,BASE: allow to create zip modules
|
|
||||||
BASE: add right-to-left
|
|
||||||
KERNEL: copy now ignore technical values ('create_date', 'create_uid', 'write_date' and 'write_uid')
|
|
||||||
ACCOUNT_TAX_INCLUDE: Now the module manage correctly the case when the taxes defined on the product differ from the taxes defined on the invoice line
|
|
||||||
ALL: fix colspan 3 -> 4
|
|
||||||
KERNEL: use context for search
|
|
||||||
ACCOUNT: improve speed of analytic account
|
|
||||||
ACCOUNT: fix search debit/credit on partner
|
|
||||||
ACCOUNT: fix refund invoice if no product_id nor uos_id on lines
|
|
||||||
MRP: fix scheduler location of product to produce and method, date of automatic orderpoint
|
|
||||||
KERNEL: many2many : fix unlink and link action
|
|
||||||
MRP: add default product_uom from context and add link from product to bom
|
|
||||||
PROJECT: improve speed for function fields
|
|
||||||
ALL: remove bad act_window name
|
|
||||||
KERNEL: modification for compatibility with postgres 7.4
|
|
||||||
KERNEL: fix size for selection field
|
|
||||||
KERNEL: fix compatibility for python2.5
|
|
||||||
KERNEL: add new win32 build script
|
|
||||||
KERNEL: add test for duplicate report and wizard
|
|
||||||
ACCOUNT: force round amount fixed in payment term
|
|
||||||
KERNEL: fix print screen
|
|
||||||
CRM: Better ergonomy
|
|
||||||
SERVER: add sum tag on tree view that display sum of the selected lines
|
|
||||||
KERNEL: allow subfield query on one2many
|
|
||||||
KERNEL: fix create_date and write_date as there are timestamp now
|
|
||||||
SERVER: improve language
|
|
||||||
KERNEL: fix search on fields function of type one2many, many2many
|
|
||||||
ACCOUNT: fix pay invoice to use period
|
|
||||||
ACCOUNT: add check recursion in account.tax.code
|
|
||||||
MRP: fix compute cycle for workcenter
|
|
||||||
BASE: add constraint uniq module name
|
|
||||||
BASE: improve update module list
|
|
||||||
ACCOUNT: add round to last payment term
|
|
||||||
KERNEL: don't modify the args of the call
|
|
||||||
KERNEL: don't use mutable as default value in function defintion
|
|
||||||
KERNEL: fix orm for sql query with reserved words
|
|
||||||
|
|
||||||
16/03/2007
|
|
||||||
4.0.3
|
|
||||||
Summary:
|
|
||||||
Improve the migration scripts
|
|
||||||
Some bugfixes
|
|
||||||
Print workflow on win32 (with ghostscript)
|
|
||||||
|
|
||||||
Bugfixes:
|
|
||||||
BASE: Fix "set default value"
|
|
||||||
HR_TIMESHEET_INVOICE: Improve invoice on timesheet
|
|
||||||
ACCOUNT: Fix tax amount
|
|
||||||
KERNEL: correct the delete for property
|
|
||||||
PURCHASE: fix the journal for invoice created by PO
|
|
||||||
KERNEL: fix the migration for id removed
|
|
||||||
Add id to some menuitem
|
|
||||||
BASE: prevent exception in ir_cron when the DB is dropped
|
|
||||||
HR: Fix sign-in/sign-out, the user is now allowed to provide a date in
|
|
||||||
the future
|
|
||||||
SALE: fix uos for the tax amount
|
|
||||||
MRP: fix wrong dbname in _procure_confirm
|
|
||||||
HR_EXPENSE: add domain to analytic_account
|
|
||||||
ACCOUNT: fix debit_get
|
|
||||||
SUBSCRIPTION: model is required now
|
|
||||||
HR_TIMESHEET: add rounding value to report
|
|
||||||
SALE: Fix cancel and recreate invoice, now cancel also the order lines
|
|
||||||
STOCK: use the tax define in sale for the invoice
|
|
||||||
ACCOUNT: add test to pay only if invoice not in state draft
|
|
||||||
KERNEL: root have access to all records
|
|
||||||
REPORT: fix rml translation to translate before the eval
|
|
||||||
ACCOUNT_TAX_INCLUDE: Use the right amount in account mmove generate
|
|
||||||
with tax_include
|
|
||||||
BASE: Improve the workflow print
|
|
||||||
SALE: Fix workflow error when creating invoice from the wizard
|
|
||||||
PRODUCT_EXTENDED: don't use pricelist to compute standard price
|
|
||||||
MRP: Use company currency for product cost structure
|
|
||||||
KERNEL: fix where clause when deleting false items
|
|
||||||
ACCOUNT: product source account depend on the invoice type now
|
|
||||||
ACCOUNT: use the property account tax for the on_change_product
|
|
||||||
ACCOUNT: use the invoice date for the date of analytic line
|
|
||||||
ACCOUNT: Fix the pay invoice when multi-currency
|
|
||||||
HR_TIMESHEET_PROJECT: use the right product
|
|
||||||
STOCK: Fix to assign picking with product consumable and call the
|
|
||||||
workflow
|
|
||||||
STOCK: Fix the split lot production
|
|
||||||
PURCHASE: fix workflow for purchase with manual invoice to not set
|
|
||||||
invoice and paid
|
|
||||||
DELIVERY: can use any type of journal for invoice
|
|
||||||
KERNEL: fix search on xxx2many
|
|
||||||
ACCOUNT: add id to sequence record
|
|
||||||
KERNEL: set properly the demo flag for module installed
|
|
||||||
KERNEL: Fix print workflow on win32
|
|
||||||
LETTER: fix print letter
|
|
||||||
|
|
||||||
Migration:
|
|
||||||
Fix migration for postreSQL 7.4
|
|
||||||
Fix the default value of demo in module
|
|
||||||
Fix migration of account_uos to product_uos
|
|
||||||
|
|
||||||
Wed Jan 17 15:06:07 CET 2007
|
|
||||||
4.0.2
|
|
||||||
Summary:
|
|
||||||
Improve the migration
|
|
||||||
Some bugfixes
|
|
||||||
Improve tax
|
|
||||||
|
|
||||||
Bugfixes:
|
|
||||||
Fix tax for invoice, refund, etc
|
|
||||||
SALE: fix view priority
|
|
||||||
PURCHASE: wizard may crash on some data
|
|
||||||
BASE: Fix import the same lang
|
|
||||||
BASE: start the cron
|
|
||||||
PURCHASE: fix domain for pricelist
|
|
||||||
KERNEL: fix object set without values
|
|
||||||
REPORT_SALE: fix for sale without line
|
|
||||||
KERNEL: add pidfile
|
|
||||||
BASE: remove 'set' for python2.3 compliant
|
|
||||||
Migration:
|
|
||||||
Migrate hr_timesheet user_id
|
|
||||||
|
|
||||||
Fri Dec 22 12:01:26 CET 2006
|
|
||||||
4.0.1
|
|
||||||
Summary:
|
|
||||||
Improve the migration
|
|
||||||
Some bugfixes
|
|
||||||
|
|
||||||
Bugfixes:
|
|
||||||
HR_EXPENSE: Fix domain
|
|
||||||
HR_TIMESHEET: Fix employee without product
|
|
||||||
TOOLS: Fix export translate
|
|
||||||
BASE: fix for concurrency of sequence number
|
|
||||||
MRP: fix report
|
|
||||||
CRM: fix graph report
|
|
||||||
KERNEL: fix instance of osv_pool
|
|
||||||
KERNEL: fix setup.py
|
|
||||||
|
|
||||||
|
|
||||||
Mon Dec 4 18:01:55 CET 2006
|
|
||||||
4.0.0
|
|
||||||
Summary:
|
|
||||||
Some bugfixes
|
|
||||||
|
|
||||||
Tue Nov 28 14:44:20 CET 2006
|
|
||||||
4.0.0-rc1
|
|
||||||
Summary:
|
|
||||||
This is a stable version (RC1) with lots of new features. Main
|
|
||||||
Improvements were:
|
|
||||||
Accounting: more functions, new modules, more stable
|
|
||||||
Much more better ergonomy
|
|
||||||
Lots of simplification to allows non IT people to use and
|
|
||||||
configure Tiny ERP: manage database, step by step configuration
|
|
||||||
menu, auto-installers, better help, ...
|
|
||||||
|
|
||||||
New:
|
|
||||||
Skill management module
|
|
||||||
ACCOUNT:
|
|
||||||
New and simpler bank statement form
|
|
||||||
New reports:
|
|
||||||
on Timesheets (analytic accounting)
|
|
||||||
Theorical revenue based on time spent
|
|
||||||
Global timesheet report by month
|
|
||||||
Chart of accounts
|
|
||||||
Different taxes methods supported
|
|
||||||
Gross (brut)
|
|
||||||
Net
|
|
||||||
Fixed amount
|
|
||||||
INVOICE:
|
|
||||||
invoice on shipping (manufacturing industry)
|
|
||||||
invoice on timesheet (services)
|
|
||||||
PURCHASE:
|
|
||||||
different invoicing control method (on order, on shipping,
|
|
||||||
manual)
|
|
||||||
Support of prices tax included /excluded in sales orders
|
|
||||||
New modules:
|
|
||||||
Sale_journal, stock_journal for bigger industries:
|
|
||||||
Divide works in different journals
|
|
||||||
New invoicing method from partner, to so, to picking
|
|
||||||
Daily, Monthly (grouped by partner or not)
|
|
||||||
New modules for prices with taxes included / excluded
|
|
||||||
New chart of accounts supported:
|
|
||||||
l10n_be/ l10n_chart_be_frnl/
|
|
||||||
l10n_chart_id/ l10n_chart_uk/
|
|
||||||
l10n_ca-qc/ l10n_chart_br/
|
|
||||||
l10n_chart_it/ l10n_chart_us_general/
|
|
||||||
l10n_ch/ l10n_chart_ca_en/
|
|
||||||
l10n_chart_it_cc2424/ l10n_chart_us_manufacturing/
|
|
||||||
l10n_ch_pcpbl_association/ l10n_chart_ca_fr/
|
|
||||||
l10n_chart_la/ l10n_chart_us_service/
|
|
||||||
l10n_ch_pcpbl_independant/ l10n_chart_ch_german/
|
|
||||||
l10n_chart_nl/ l10n_chart_us_ucoa/
|
|
||||||
l10n_ch_pcpbl_menage/ l10n_chart_cn/
|
|
||||||
l10n_chart_nl_standard/ l10n_chart_us_ucoa_ez/
|
|
||||||
l10n_ch_pcpbl_plangen/ l10n_chart_cn_traditional/
|
|
||||||
l10n_chart_no/ l10n_chart_ve/
|
|
||||||
l10n_ch_pcpbl_plangensimpl/ l10n_chart_co/
|
|
||||||
l10n_chart_pa/ l10n_fr/
|
|
||||||
l10n_ch_vat_brut/ l10n_chart_cz/
|
|
||||||
l10n_chart_pl/ l10n_se/
|
|
||||||
l10n_ch_vat_forfait/ l10n_chart_da/
|
|
||||||
l10n_chart_sp/ l10n_simple/
|
|
||||||
l10n_ch_vat_net/ l10n_chart_de_datev_skr03/
|
|
||||||
l10n_chart_sw/
|
|
||||||
l10n_chart_at/ l10n_chart_de_skr03/
|
|
||||||
l10n_chart_sw_church/
|
|
||||||
l10n_chart_au/ l10n_chart_hu/
|
|
||||||
l10n_chart_sw_food/
|
|
||||||
Step by step configuration menu
|
|
||||||
Setup wizard on first connection
|
|
||||||
Select a company profile, auto-install language, demo data, ...
|
|
||||||
|
|
||||||
Imrovements:
|
|
||||||
KERNEL: Demo data improved
|
|
||||||
Better import / export system
|
|
||||||
KERNEL: Multi-database management system
|
|
||||||
Backup, Restore, Create, Drop from the client
|
|
||||||
PRODUCT/PRODUCT_EXTD: Eavily change the product form, use the new
|
|
||||||
object to compute the pricelist
|
|
||||||
REPORTS:
|
|
||||||
Better Sale order, purchase order, invocies and customers reports
|
|
||||||
ACCOUNT: Support of taxes in accounts
|
|
||||||
management of the VAT taxes for most european countries:
|
|
||||||
Support of VAT codes in invoices
|
|
||||||
Better computation of default values in accounting entries
|
|
||||||
Preferences in partners, override products
|
|
||||||
Bugfix when closing a fiscal year
|
|
||||||
Better ergonomy when writting entries
|
|
||||||
New Module Management System:
|
|
||||||
Install / Upgrade new modules directly from the client
|
|
||||||
Install new languages
|
|
||||||
KERNEL:
|
|
||||||
Ability to add select=True at the object level for postgresql indexes
|
|
||||||
Bugfix in search in some inherited objects
|
|
||||||
Added the ability to call methods from a browse object
|
|
||||||
KERNEL+BASE: changed the way the migration system works for menuitems:
|
|
||||||
now you can change a menuitem defined elsewhere. And this will work
|
|
||||||
whether that menuitem has an id or not (it use the name of the
|
|
||||||
menuitem to find it)
|
|
||||||
KERNEL:
|
|
||||||
Installing a module from the client
|
|
||||||
Better Windows Auto-Installer
|
|
||||||
DELIVERY:
|
|
||||||
Delivery and invoicing on picking list
|
|
||||||
KERNEL:
|
|
||||||
Distinction between active (by default) and installable
|
|
||||||
ACCOUNT/PROJECT: Added support for the type of invoicing
|
|
||||||
CRM:
|
|
||||||
eMAil gateway
|
|
||||||
Management of different departments and sections
|
|
||||||
Rule system
|
|
||||||
About 20 new statistics reporting
|
|
||||||
eCommerce interface:
|
|
||||||
Better Joomla (virtuemart, OSCommerce) support
|
|
||||||
Joomla is now fully functionnal
|
|
||||||
|
|
||||||
Bugfixes:
|
|
||||||
ACCOUNT: tree view on reporting analytic account
|
|
||||||
KERNEL: Fix the bug that happened when mixing active and child_of
|
|
||||||
search
|
|
||||||
KERNEL: Check for the existance of active when computing child_of
|
|
||||||
PRODUCT: production computation with different UoM
|
|
||||||
|
|
||||||
------------------------------------------------------------------------
|
|
||||||
|
|
||||||
Fri Oct 6 14:44:05 CEST 2006
|
|
||||||
Server 3.4.2
|
|
||||||
Improvements:
|
|
||||||
BASE: changed workflow print system so that it handles inexisting
|
|
||||||
workflows more gracefully (patch from Geoff Gardiner)
|
|
||||||
MRP: new view to take into account the orderpoint exceptions
|
|
||||||
MRP: made menu title more explicit
|
|
||||||
|
|
||||||
Bugfixes:
|
|
||||||
ACCOUNT: fixed typo in invoice + changed sxw file so that it is in
|
|
||||||
sync with the rml file
|
|
||||||
DELIVERY: fixed taxes on delivery line (patch from Brice Vissière)
|
|
||||||
PROJECT: skip tasks without user in Gantt charts (it crashed the report)
|
|
||||||
PRODUCT: fixed bug when no active pricelist version was found
|
|
||||||
PRODUCT_EXTENDED: correct recursive computation of the price
|
|
||||||
SALE: get product price from price list even when quantity is set after
|
|
||||||
the product is set
|
|
||||||
STOCK: fixed partial picking
|
|
||||||
|
|
||||||
Packaging:
|
|
||||||
Changed migration script so that it works on PostgreSQL 7.4
|
|
||||||
|
|
||||||
------------------------------------------------------------------------
|
|
||||||
|
|
||||||
Tue Sep 12 15:10:31 CEST 2006
|
|
||||||
Server 3.4.1
|
|
||||||
Bugfixes:
|
|
||||||
ACCOUNT: fixed a bug which prevented to reconcile posted moves.
|
|
||||||
|
|
||||||
------------------------------------------------------------------------
|
|
||||||
|
|
||||||
Mon Sep 11 16:12:10 CEST 2006
|
|
||||||
Server 3.4.0 (changes since 3.3.0)
|
|
||||||
New modules:
|
|
||||||
ESALE_JOOMLA: integration with Joomla CMS
|
|
||||||
HR_TIMESHEET_ICAL: import iCal to automatically complete timesheet
|
|
||||||
based on outlook meetings
|
|
||||||
PARTNER_LDAP: adds partner synchronization with an LDAP server
|
|
||||||
SALE_REBATE: adds rebates to sale orders
|
|
||||||
|
|
||||||
4 new modules for reporting using postgresql views:
|
|
||||||
REPORT_CRM: reporting on CRM cases: by month, user, ...
|
|
||||||
REPORT_PROJECT: reporting on projects: tasks closed by project, user,
|
|
||||||
month, ...
|
|
||||||
REPORT_PURCHASE: reporting on purchases
|
|
||||||
REPORT_SALE: reporting on sales by periods and by product, category of
|
|
||||||
product, ...
|
|
||||||
|
|
||||||
New features:
|
|
||||||
KERNEL: Tiny ERP server and client may now communicate through HTTPS.
|
|
||||||
To launch the server with HTTPS, use the -S or --secure option
|
|
||||||
Note that if the server runs on HTTPS, the clients MUST connect
|
|
||||||
with the "secure" option checked.
|
|
||||||
KERNEL: the server can now run as a service on Windows
|
|
||||||
Printscreen function (Tree view print)
|
|
||||||
KERNEL: added a new --stop-after-init option which stops the server
|
|
||||||
just before it starts listening
|
|
||||||
KERNEL: added support for a new forcecreate attribute on XML record
|
|
||||||
fields: it is useful for records are in a data node marked as
|
|
||||||
"noupdate" but the record still needs to be added if it doesn't
|
|
||||||
exit yet. The typical use for that is when you add a new record
|
|
||||||
to a noupdate file/node.
|
|
||||||
KERNEL: manage SQL constraints with human-readable error message on the
|
|
||||||
client side, eg: Unique constraints
|
|
||||||
KERNEL: added a new system to be able to specify the tooltip for each
|
|
||||||
field in the definition of the field (by using the new help=""
|
|
||||||
attribute)
|
|
||||||
ACCOUNT: new report: aged trial balance system
|
|
||||||
ACCOUNT: added a wizard to pay an invoice from the invoice form
|
|
||||||
BASE: print on a module to print the reference guide using introspection
|
|
||||||
HR: added report on attendance errors
|
|
||||||
PRODUCT: products now support multi-Level variants
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
KERNEL: speed improvement in many parts of the system thanks to some
|
|
||||||
optimizations and a new caching system
|
|
||||||
KERNEL: New property system which replace the, now deprecated, ir_set
|
|
||||||
system. This leads to better migration of properties, more
|
|
||||||
practical use of them (they can be used like normal fields),
|
|
||||||
they can be translated, they are "multi-company aware", and
|
|
||||||
you can specify access rights for them on a per field basis.
|
|
||||||
KERNEL: Under windows, the server looks for its configuration file in
|
|
||||||
the "etc" sub directory (relative to the installation path).
|
|
||||||
This was needed so that the server can be run as a windows
|
|
||||||
service (using the SYSTEM profile).
|
|
||||||
KERNEL: added ability to import CSV files from the __terp__.py file
|
|
||||||
KERNEL: force freeing cursor when closing them, so that they are
|
|
||||||
available again immediately and not when garbage collected.
|
|
||||||
KERNEL: automatically drop not null/required constraints from removed
|
|
||||||
fields (ie which are in the database but not in the object)
|
|
||||||
KERNEL: added a command-line option to specify which smtp server to use
|
|
||||||
to send emails.
|
|
||||||
KERNEL: made browse_record hashable
|
|
||||||
ALL: removed shortcuts for the demo user.
|
|
||||||
ACCOUNT: better invoice report
|
|
||||||
ACCOUNT: Modifs for account chart, removed old stock_income account type
|
|
||||||
ACCOUNT: made the test_paid method on invoices more tolerant to buggy
|
|
||||||
data (open invoices without move/movelines)
|
|
||||||
ACCOUNT: better bank statement reconciliation system
|
|
||||||
ACCOUNT: accounting entries encoding improved a lot (using journal)
|
|
||||||
ACCOUNT: Adding a date and max Qty field in analytic accounts for
|
|
||||||
support contract
|
|
||||||
ACCOUNT: Adding the View type to analytic account / cost account
|
|
||||||
ACCOUNT: changed test_paid so that the workflow works even if there is
|
|
||||||
no move line
|
|
||||||
ACCOUNT: Cleanup credit/debit and balance computation methods. Should
|
|
||||||
be faster too.
|
|
||||||
ACCOUNT: use the normal sequence (from the journal) for the name of
|
|
||||||
moves generated from invoices instead of the longer name.
|
|
||||||
ACCOUNT: print Payment delay in invoices
|
|
||||||
ACCOUNT: account chart show subtotals
|
|
||||||
ACCOUNT: Subtotal in view accounts
|
|
||||||
ACCOUNT: Replaced some Typo: moves-> entries, Transaction -> entry
|
|
||||||
ACCOUNT: added quantities in analytic accounts view, and modified
|
|
||||||
cost ledger report for partners/customers
|
|
||||||
ACCOUNT: added default value for the currency field in invoices
|
|
||||||
ACCOUNT: added the comment/notes field on the invoice report
|
|
||||||
BASE: added menuitem (and action) to access partner functions (in the
|
|
||||||
definitions menu)
|
|
||||||
BASE: better demo data
|
|
||||||
BASE: duplicating a menu item now duplicates its action and submenus
|
|
||||||
BASE: Bank Details on Partners
|
|
||||||
CRM: View on all actions made on cases (used by our ISO9002 customer
|
|
||||||
to manage corrections to actions)
|
|
||||||
CRM: fixed wizard to create a sale order from a case
|
|
||||||
CRM: search on non active case, not desactivated by default
|
|
||||||
CRM: Case ID in fields with search
|
|
||||||
HR_TIMESHEET: new "sign_in, sign_out" using projects. It fills
|
|
||||||
timesheets and attendance at the same time.
|
|
||||||
HR_TIMESHEET: added cost unit to employee demo data
|
|
||||||
MRP: improvement in the scheduler
|
|
||||||
MRP: purchase order lines' description generated from a procurement
|
|
||||||
defaults to the product name instead of procurement name
|
|
||||||
MRP: Better traceability
|
|
||||||
MRP: Better view for procurement in exception
|
|
||||||
MRP: Added production delay in product forms. Use this delay for
|
|
||||||
average production delay for one product
|
|
||||||
MRP: dates scheduler, better computation
|
|
||||||
MRP: added constraint for non 0 BoM lines
|
|
||||||
PRODUCT: Better pricelist system (on template or variant of product)
|
|
||||||
PRODUCT_EXTENDED: Compute the price only if there is a supplier
|
|
||||||
PROJECT: when a task is closed, use the task's customer to warn the
|
|
||||||
customer if it is set, otherwise use the project contact.
|
|
||||||
PROJECT: better system to automatically send an email to the customer
|
|
||||||
when a task is closed or reopened.
|
|
||||||
PURCHASE: date_planned <= current_time line in red
|
|
||||||
PURCHASE: better purchase order report
|
|
||||||
PURCHASE: better purchase order duplication: you can now duplicate non
|
|
||||||
draft purchase orders and the new one will become draft.
|
|
||||||
SALE: better sale order report
|
|
||||||
SALE: better demo data for sale orders
|
|
||||||
SALE: better view for buttons in sale.order
|
|
||||||
SALE: select product => description = product name instead of code
|
|
||||||
SALE: warehouse field in shop is now required
|
|
||||||
SCRUM: lots of improvements for better useability
|
|
||||||
STOCK: allows to confirm empty picking lists.
|
|
||||||
STOCK: speed up stock computation methods
|
|
||||||
|
|
||||||
Bugfixes:
|
|
||||||
KERNEL: fix a huge bug in the search method for objects involving
|
|
||||||
"old-style" inheritance (inherits) which prevented some records
|
|
||||||
to be accessible in some cases. Most notable example was some
|
|
||||||
products were not accessible in the sale order lines if you had
|
|
||||||
more products in your database than the limit of your search
|
|
||||||
(80 by default).
|
|
||||||
KERNEL: fixed bug which caused OO (sxw) reports to behave badly (crash
|
|
||||||
on Windows and not print correctly on Linux) when data
|
|
||||||
contained XML entities (&, <, >)
|
|
||||||
KERNEL: reports are now fully concurrency compliant
|
|
||||||
KERNEL: fixed bug which caused menuitems without id to cause havoc on
|
|
||||||
update. The menuitems themselves were not created (which is
|
|
||||||
correct) but they created a bad "default" action for all
|
|
||||||
menuitems without action (such as all "menu folders").
|
|
||||||
KERNEL: fix a small security issue: we should check the password of the
|
|
||||||
user when a user asks for the result of a report (in addition
|
|
||||||
to the user id and id of that report)
|
|
||||||
KERNEL: bugfix in view inheritancy
|
|
||||||
KERNEL: fixed duplicating resource with a state field whose selection
|
|
||||||
doesn't contain a 'draft' value (for example project tasks). It
|
|
||||||
now uses the default value of the resource for that field.
|
|
||||||
KERNEL: fixed updating many2many fields using the (4, id) syntax
|
|
||||||
KERNEL: load/save the --logfile option correctly in the config file
|
|
||||||
KERNEL: fixed duplicating a resource with many2many fields
|
|
||||||
ALL: all properties should be inside a data tag with "noupdate" and
|
|
||||||
should have a forcecreate attribute.
|
|
||||||
ACCOUNT: fixed rounding bug in tax computation method
|
|
||||||
ACCOUNT: bugfix in balance and aged balance reports
|
|
||||||
ACCOUNT: fixing precision in function fields methods
|
|
||||||
ACCOUNT: fixed creation of account move lines without using the client
|
|
||||||
interface
|
|
||||||
ACCOUNT: fixed duplicating invoices
|
|
||||||
ACCOUNT: fixed opening an invoices whose description contained non
|
|
||||||
ASCII chars at specific position
|
|
||||||
ACCOUNT: small bugfixes in all accounting reports
|
|
||||||
ACCOUNT: fixed crash when --without-demo due to missing payment.term
|
|
||||||
ACCOUNT: fixed bug in automatic reconciliation
|
|
||||||
ACCOUNT: pass the address to the tax computation method so that it is
|
|
||||||
available in the tax "python applicable code"
|
|
||||||
BASE: allows to delete a request which has a history (it now deletes the
|
|
||||||
history as well as the request)
|
|
||||||
BASE: override copy method for users so that we can duplicate them
|
|
||||||
BASE: fixed bug when the user search for a partner by hitting on an
|
|
||||||
empty many2one field (it searched for a partner with ref=='')
|
|
||||||
BASE: making ir.sequence call thread-safe.
|
|
||||||
CRM: fixed a bug which introduced an invalid case state when closing a
|
|
||||||
case (Thanks to Leigh Willard)
|
|
||||||
HR: added domain to category tree view so that they are not displayed
|
|
||||||
twice
|
|
||||||
HR_TIMESHEET: fixed print graph
|
|
||||||
HR_TIMESHEET: fixed printing timesheet report
|
|
||||||
HR_TIMESHEET: Remove a timesheet entry removes the analytic line
|
|
||||||
MRP: bugfix on "force reservation"
|
|
||||||
MRP: fixed bugs in some reports and MRP scheduler when a partner has
|
|
||||||
no address
|
|
||||||
MRP: fix Force production button if no product available
|
|
||||||
MRP: when computing lots of procurements, the scheduler could raise
|
|
||||||
locking error at the database level. Fixed.
|
|
||||||
PRODUCT: added missing context to compute product list price
|
|
||||||
PRODUCT: fixed field type of qty_available and virtual_available
|
|
||||||
(integer->float). This prevented these fields to be displayed
|
|
||||||
in forms.
|
|
||||||
PROJECT: fixed the view of unassigned task (form and list) instead of
|
|
||||||
form only.
|
|
||||||
PURCHASE: fixed merging orders that made inventory errors when coming
|
|
||||||
from a procurement (orderpoint).
|
|
||||||
PURCHASE: fix bug which prevented to make a purchase order with
|
|
||||||
"manual" lines (ie without product)
|
|
||||||
PURCHASE: fix wizard to group purchase orders in several ways:
|
|
||||||
- only group orders if they are to the same location
|
|
||||||
- only group lines if they are the same except for qty and unit
|
|
||||||
- fix the workflow redirect method so that procurement are not
|
|
||||||
canceled when we merge orders
|
|
||||||
SALE: fixed duplicating a confirmed sale order
|
|
||||||
SALE: fixed making sale orders with "manual" lines (without product)
|
|
||||||
STOCK: future stock prevision bugfix (for move when date_planned < now)
|
|
||||||
STOCK: better view for stock.move
|
|
||||||
STOCK: fixed partial pickings (waiting for a production)
|
|
||||||
Miscellaneous minor bugfixes
|
|
||||||
|
|
||||||
Packaging:
|
|
||||||
Fixed bug in setup.py which didn't copy csv files nor some sub-
|
|
||||||
directories
|
|
||||||
Added a script to migrate a 3.3.0 server to 3.4.0 (you should read the
|
|
||||||
README file in doc/migrate/3.3.0-3.4.0)
|
|
||||||
Removed OsCommerce module
|
|
||||||
|
|
||||||
------------------------------------------------------------------------
|
|
||||||
|
|
||||||
Fri May 19 10:16:18 CEST 2006
|
|
||||||
Server 3.3.0
|
|
||||||
New features:
|
|
||||||
NEW MODULE: hr_timesheet_project
|
|
||||||
Automatically maps projects and tasks to analytic account
|
|
||||||
So that hours spent closing tasks are automatically encoded
|
|
||||||
KERNEL: Added a logfile and a pidfile option (patch from Dan Horak)
|
|
||||||
STOCK: Added support for revisions of tracking numbers
|
|
||||||
STOCK: Added support for revision of production lots
|
|
||||||
STOCK: Added a "splitting and tracking lines" wizard
|
|
||||||
PRODUCT_EXTENDED: Added a method to compute the cost of a product
|
|
||||||
automatically from the cost of its parts
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
ALL: Small improvements in wizards (order of buttons)
|
|
||||||
PRODUCT: Remove packaging info from supplierinfo
|
|
||||||
PROJECT: Better task view (moved unused fields to other tab)
|
|
||||||
SALE: Keep formating for sale order lines' notes in the sale order report
|
|
||||||
|
|
||||||
Bugfixes:
|
|
||||||
KERNEL: Fixed bug which caused field names with non ascii chars didn't work
|
|
||||||
in list mode on Windows
|
|
||||||
KERNEL: Fix concurrency issue with UpdatableStr with the use of
|
|
||||||
threading.local
|
|
||||||
KERNEL: Removed browse_record __unicode__ method... It made the sale order
|
|
||||||
report crash when using product names with non ASCII characters
|
|
||||||
KERNEL: Fixed bug which caused the translation export to fail when the server
|
|
||||||
was not launched from the directory its source is.
|
|
||||||
BASE: Updating a menuitem now takes care its parent menus
|
|
||||||
BASE: Fixed a cursor locking issue with updates
|
|
||||||
BASE: Fixed viewing sequence types as a tree/list
|
|
||||||
HR: Month field needs to be required in the "hours spent" report
|
|
||||||
PURCHASE: fixed group purchase order wizard:
|
|
||||||
- if there were orders from several different suppliers, it created a purchase
|
|
||||||
order for only the first supplier but canceled other orders, even those which
|
|
||||||
weren't merged in the created order (closes bugzilla #236)
|
|
||||||
- doesn't trash "manual" lines (ie lines with no product)
|
|
||||||
- pay attentions to unit factors when adding several lines together
|
|
||||||
MRP: fixed workcenter load report (prints only the selected workcenters) and
|
|
||||||
does't crash if the user didn't select all workcenters
|
|
||||||
|
|
||||||
Miscellaneous:
|
|
||||||
Removed pydot from required dependencies
|
|
||||||
|
|
||||||
------------------------------------------------------------------------
|
|
||||||
|
|
||||||
Server 3.3.0-rc1
|
|
||||||
================
|
|
||||||
|
|
||||||
Changelog for Users
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
New module: OS Commerce
|
|
||||||
Integration with Tiny ERP and OS Commerce
|
|
||||||
Synchronisation 100% automated with eSale;
|
|
||||||
Import of categories of products
|
|
||||||
Export of products (with photos support)
|
|
||||||
Import of Orders (with the eslae module)
|
|
||||||
Export of stock level
|
|
||||||
Import of OSCommerce Taxes
|
|
||||||
Multiple shop allowed with different rules/products
|
|
||||||
Simple Installation
|
|
||||||
|
|
||||||
New Module: HR_TIMESHEET
|
|
||||||
Management by affair, timesheets creates analytic entries in the
|
|
||||||
accounting to get costs and revenue of each affairs. Affairs are
|
|
||||||
structured in trees.
|
|
||||||
|
|
||||||
New Module: Account Follow Up
|
|
||||||
Multi-Level and configurable Follows ups for the accounting module
|
|
||||||
|
|
||||||
New module; Productivity Analysis of users
|
|
||||||
A module to compare productivity of users of Tiny ERP
|
|
||||||
Generic module, you can compare everything (sales, products, partners,
|
|
||||||
...)
|
|
||||||
|
|
||||||
New Modules for localisations:
|
|
||||||
Accounting localisations for be, ca, fr, de, ch, sw
|
|
||||||
Fix: corrected encoding (latin1 to utf8) of Swedish account tree XML file
|
|
||||||
|
|
||||||
New Module - Sandwich
|
|
||||||
Allows employees to order the lunch
|
|
||||||
Keeps employees preferences
|
|
||||||
|
|
||||||
New Module TOOLS:
|
|
||||||
Email automatic importation/integration in the ERP
|
|
||||||
|
|
||||||
New Module EDI:
|
|
||||||
Import of EDI sale orders
|
|
||||||
Export of shippings
|
|
||||||
|
|
||||||
Multi-Company:
|
|
||||||
Tiny ERP is now fully multi-company !
|
|
||||||
New Company and configuration can be made in the client side.
|
|
||||||
|
|
||||||
ACCOUNTING:
|
|
||||||
Better Entries > Standard Entries (Editable Tree, like in Excel)
|
|
||||||
Automatic creation of lines
|
|
||||||
Journal centralised or not
|
|
||||||
Counterpart of lines in one line or one counterpart per entry
|
|
||||||
Analytic accounting recoded from scratch
|
|
||||||
5 new reports
|
|
||||||
Completly integrated with:
|
|
||||||
production,
|
|
||||||
hr_timesheet > Management by affairs
|
|
||||||
sales & purchases,
|
|
||||||
Tasks.
|
|
||||||
Added unreconciliation functionnalities
|
|
||||||
Added account tree fast rendering
|
|
||||||
Better tax computation system supporting worldwide specific countries
|
|
||||||
Better subscription system
|
|
||||||
Wizard to close a period
|
|
||||||
Wizard to clase a fiscal year
|
|
||||||
Very powerfull, simple and complete multi-currency system
|
|
||||||
in pricelists, sale order, purchases, ...
|
|
||||||
Added required fields in currencies (currency code)
|
|
||||||
Added decimal support
|
|
||||||
Better search on accounts (on code, shortcut or name)
|
|
||||||
Added constraint;
|
|
||||||
on users
|
|
||||||
on group
|
|
||||||
on accounts in a journal
|
|
||||||
added menuitem for automatic reconciliation; Multi-Levels
|
|
||||||
added factor to analytic units
|
|
||||||
added form view for budget items dotations
|
|
||||||
made number of digits in quantity field of the budget spread wizard coherent with the object field
|
|
||||||
fixed journal on purchase invoices/refunds (SugarCRM #6)
|
|
||||||
Better bank statement reconciliation
|
|
||||||
Fixed some reports
|
|
||||||
|
|
||||||
STOCK:
|
|
||||||
Better view for location (using localisation of locations; posx, posy, posz)
|
|
||||||
|
|
||||||
MARKETING:
|
|
||||||
fixed small bug when a partner has no adress
|
|
||||||
state field of marketing partner set as readonly
|
|
||||||
fixed marketing steps form view
|
|
||||||
better history view
|
|
||||||
disabled completely send sms wizard
|
|
||||||
fixed send email wizard
|
|
||||||
good priority -> high priority
|
|
||||||
fixed 'call again later' button
|
|
||||||
|
|
||||||
NETWORK:
|
|
||||||
added tree view for login/password
|
|
||||||
|
|
||||||
HR:
|
|
||||||
added holiday_status (=type of ...) to expense claim form view
|
|
||||||
|
|
||||||
BASE (partner):
|
|
||||||
fixed email_send and _email_send methods
|
|
||||||
removed partner without addresses from demo data
|
|
||||||
Added a date field in the partner form
|
|
||||||
|
|
||||||
MRP:
|
|
||||||
New report: workcenter futur loads
|
|
||||||
Analytic entries when production done.
|
|
||||||
SCHEDULER: better error msg in the generated request
|
|
||||||
Allows services in BoMs (for eg, subcontracting)
|
|
||||||
|
|
||||||
Project/Service Management:
|
|
||||||
create orders from tasks; bugfixes
|
|
||||||
Completly integrated with the rest of the ERP
|
|
||||||
Services can now be MTO/MTS, Buy (subcontracting), produce (task), ...
|
|
||||||
Services can be used anywhere (sale.order, bom, ...)
|
|
||||||
See this graph;
|
|
||||||
http://tiny.be/download/flux/flux_procurement.png
|
|
||||||
tasks sorted by ... AND id, so that the order is not random
|
|
||||||
within a priority
|
|
||||||
|
|
||||||
Automatic translations of all wizards
|
|
||||||
|
|
||||||
Scrum Project Management
|
|
||||||
Better Ergonomy; click on a sprint to view tasks
|
|
||||||
Planned, Effetive hours and progress in backlog, project and sprint
|
|
||||||
Better Burndown Chart computation
|
|
||||||
Better (simpler) view of tasks
|
|
||||||
|
|
||||||
Better demo Data
|
|
||||||
In All modules, eth converted to english
|
|
||||||
|
|
||||||
PRODUCT:
|
|
||||||
computing the weight of the packaging
|
|
||||||
Added last order date
|
|
||||||
Alternative suppliers (with delay, prefs, ...) for one product
|
|
||||||
|
|
||||||
PRICELISTS:
|
|
||||||
much more powerfull system
|
|
||||||
views simplified
|
|
||||||
one pricelist per usage: sale, order, pvc
|
|
||||||
price_type on product_view
|
|
||||||
Multi-Currency pricelist (EUR pricelist can depend on a $ one)
|
|
||||||
|
|
||||||
HR-TIMESHEET: fixed bugs in hours report:
|
|
||||||
sum all lines for the same day instead of displaying only the first one
|
|
||||||
it now uses the analytic unit factor, so that mixing hours and days has some sense
|
|
||||||
close cursor
|
|
||||||
|
|
||||||
SALE:
|
|
||||||
invoices generated from a sale order are pre-computed (taxes are computed)
|
|
||||||
|
|
||||||
new invoicing functionnality;
|
|
||||||
invoice on order quantities or,
|
|
||||||
invoice on shipped quantities
|
|
||||||
|
|
||||||
Invoice on a sale.order or a sale.order.line
|
|
||||||
|
|
||||||
added default value for uos_qty in sale order lines (default to 1)
|
|
||||||
|
|
||||||
|
|
||||||
Changelog for Developers
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
New option --debug, that opens a python interpreter when an exception
|
|
||||||
occurs on the server side.
|
|
||||||
|
|
||||||
Better wizard system. Arguements self, cr, uid, context are passed in all
|
|
||||||
functions of the wizard like normal objects. All wizards converted.
|
|
||||||
|
|
||||||
Speed improvements in many views; partners, sale.order, ...
|
|
||||||
less requests from client to server when opening a form
|
|
||||||
|
|
||||||
Better translation system, wizard terms are exported.
|
|
||||||
|
|
||||||
Script to render module dependency graph
|
|
||||||
|
|
||||||
KERNEL+ALL: pass context to methods computing a selection.
|
|
||||||
|
|
||||||
Modification for actions and view definitions:
|
|
||||||
Actions Window:
|
|
||||||
New field: view_mode = 'tree,form' or 'form,tree' -> default='form,tree'
|
|
||||||
New role of view_type: tree (with shortcuts), form (others with switch button)
|
|
||||||
If you need a form that opens in list mode:
|
|
||||||
view_mode = 'tree,form' or 'tree'
|
|
||||||
view_type = form
|
|
||||||
You can define a view in a view (for example sale.order.line in
|
|
||||||
sale.order)
|
|
||||||
less requests on the client side, no need to define 2 views
|
|
||||||
|
|
||||||
Better command-line option message
|
|
||||||
|
|
||||||
Fixed bug which prevented to search for names using non ASCII
|
|
||||||
chars in many2one or many2many fields
|
|
||||||
|
|
||||||
Report Engine: bugfix for concurrency
|
|
||||||
|
|
||||||
Support of SQL constraints
|
|
||||||
Uniq, check, ...
|
|
||||||
Good error message in the client side (check an account entry with
|
|
||||||
credit and debit >0)
|
|
||||||
|
|
||||||
Fixed: when an exception was raised, the cursor wasn't closed and this
|
|
||||||
could cause a freeze in some cases
|
|
||||||
|
|
||||||
Sequence can contains code: %(year)s, ... for prefix, suffix
|
|
||||||
EX: ORDER %(year)/0005
|
|
||||||
|
|
||||||
Bugfixes for automatic migration system
|
|
||||||
|
|
||||||
bugfix on default value with creation of inherits
|
|
||||||
|
|
||||||
Improvement in report_sxw; you can redefine preprocess to do some
|
|
||||||
preprocessing before printing
|
|
||||||
|
|
||||||
Barcode support enabled by default
|
|
||||||
|
|
||||||
Fixed OpenOffice reports when the server is not launched from the
|
|
||||||
directory the code reside
|
|
||||||
|
|
||||||
Print workflow use a pipe instead of using a temporary file (now workflows
|
|
||||||
works on Windows Servers)
|
|
||||||
|
|
||||||
Inheritancy improved (multiple arguments: replace, inside, after, before)
|
|
||||||
|
|
||||||
Lots of small bugfixes
|
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
2010-07-XX: 6.0.0
|
|
||||||
=================
|
|
||||||
|
|
||||||
Improvements (server)
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
* support of 'ref' attribute for importing 'reference' field values, as for many2one fields.
|
|
||||||
* experimental xml2yml script in /tools for conversion of XML data/test files to the new YAML format
|
|
||||||
|
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
OpenERP Quick Installation Guide
|
|
||||||
---------------------------------
|
|
||||||
|
|
||||||
This file contains a quick guide to configure and install the OpenERP server.
|
|
||||||
|
|
||||||
Required dependencies:
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
You need the following software installed:
|
|
||||||
|
|
||||||
* Python 2.5 or 2.6
|
|
||||||
* Postgresql 8.2 or above
|
|
||||||
* Psycopg2 python module
|
|
||||||
* Reportlab pdf generation library for python
|
|
||||||
* lxml python module
|
|
||||||
* pytz python module
|
|
||||||
* PyYaml python module (install with: easy_install PyYaml)
|
|
||||||
|
|
||||||
Some dependencies are only required for specific purposes:
|
|
||||||
|
|
||||||
for rendering workflows graphs, you need:
|
|
||||||
* graphviz
|
|
||||||
* pyparsing
|
|
||||||
|
|
||||||
For Luxembourg localization, you also need:
|
|
||||||
* pdftk (http://www.pdflabs.com/tools/pdftk-the-pdf-toolkit/)
|
|
||||||
|
|
||||||
for generating reports using non .jpg images, you need:
|
|
||||||
* Python Imaging Library for python
|
|
||||||
|
|
||||||
For Debian-based distributions, the required packages can be installed with the
|
|
||||||
following command:
|
|
||||||
|
|
||||||
#> apt-get install -y postgresql graphviz python-psycopg2 python-lxml python-tz python-imaging
|
|
||||||
|
|
||||||
For Fedora
|
|
||||||
if they are not installed, install:
|
|
||||||
python and postgresql
|
|
||||||
|
|
||||||
uses yum or you can recover required packages on fedora web site in "core" or "extra" repository :
|
|
||||||
postgresql-python
|
|
||||||
python-lxml
|
|
||||||
python-imaging
|
|
||||||
python-psycopg2
|
|
||||||
python-reportlab
|
|
||||||
graphviz
|
|
||||||
You can find pyparsing at http://pyparsing.sourceforge.net/
|
|
||||||
|
|
||||||
1. Check that all the required dependencies are installed.
|
|
||||||
|
|
||||||
2. Launch the program "python ./bin/openerp-server.py -r db_user -w db_password --db_host 127.0.0.1".
|
|
||||||
See the man page for more information about options.
|
|
||||||
|
|
||||||
3. Connect to the server using the GUI client. And follow the instructions to create a new database.
|
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
Important note for OpenERP build >= 5.0.1-11xrg:
|
|
||||||
|
|
||||||
THE USERNAME HAS CHANGED!!
|
|
||||||
|
|
||||||
Former user "tinyerp" is now called "openerp".
|
|
||||||
|
|
||||||
|
|
||||||
For that, you will have to make sure the following files are chowned
|
|
||||||
to the new user:
|
|
||||||
|
|
||||||
/var/log/openerp
|
|
||||||
/var/spool/openerp
|
|
||||||
/var/run/openerp
|
|
||||||
/etc/openerp
|
|
||||||
/etc/openerp/cert.cfg
|
|
||||||
/etc/openerp-server.conf
|
|
||||||
/etc/logrotate.d/openerp-server
|
|
||||||
|
|
||||||
Then, rename the user in the postgres database:
|
|
||||||
|
|
||||||
psql -U postgres postgres
|
|
||||||
|
|
||||||
ALTER ROLE tinyerp RENAME TO openerp;
|
|
||||||
|
|
||||||
Then, edit your openerp-server.conf to depict the change:
|
|
||||||
- db_user = tinyerp
|
|
||||||
+ db_user = openerp
|
|
||||||
|
|
||||||
Good luck!
|
|
||||||
|
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
ADMIN_PASSWD='admin'
|
|
||||||
method_1() {
|
|
||||||
cat '-' << EOF
|
|
||||||
<xml>
|
|
||||||
<methodCall>
|
|
||||||
<methodName>set_loglevel</methodName>
|
|
||||||
<params>
|
|
||||||
<param><value><string>$ADMIN_PASSWD</string></value>
|
|
||||||
</param>
|
|
||||||
<param>
|
|
||||||
<value><string>$1</string></value>
|
|
||||||
</param>
|
|
||||||
</params>
|
|
||||||
</methodCall>
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
LEVEL=10
|
|
||||||
|
|
||||||
if [ -n "$1" ] ; then LEVEL=$1 ; fi
|
|
||||||
|
|
||||||
method_1 $LEVEL | POST -c 'text/xml' http://localhost:8069/xmlrpc/common
|
|
||||||
#eof
|
|
|
@ -1,121 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# OpenERP, Open Source Management Solution
|
|
||||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as
|
|
||||||
# published by the Free Software Foundation, either version 3 of the
|
|
||||||
# License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
#
|
|
||||||
# This script will automatically test all profiles, localisations and language
|
|
||||||
# packs You must start the OpenERP server and not have a test database. You
|
|
||||||
# may also have to change some data in the top of this file.
|
|
||||||
#
|
|
||||||
|
|
||||||
import xmlrpclib
|
|
||||||
import time
|
|
||||||
import base64
|
|
||||||
|
|
||||||
url = 'http://localhost:8069/xmlrpc'
|
|
||||||
profiles = [
|
|
||||||
'profile_accounting',
|
|
||||||
'profile_service',
|
|
||||||
'profile_manufacturing'
|
|
||||||
]
|
|
||||||
l10n_charts = [
|
|
||||||
'l10n_uk',
|
|
||||||
False,
|
|
||||||
'l10n_be',
|
|
||||||
'l10n_fr'
|
|
||||||
]
|
|
||||||
dbname = 'test'
|
|
||||||
admin_passwd = 'admin'
|
|
||||||
lang = False # List of langs of False for all
|
|
||||||
|
|
||||||
|
|
||||||
sock = xmlrpclib.ServerProxy(url+'/object')
|
|
||||||
sock2 = xmlrpclib.ServerProxy(url+'/db')
|
|
||||||
sock3 = xmlrpclib.ServerProxy(url+'/common')
|
|
||||||
sock4 = xmlrpclib.ServerProxy(url+'/wizard')
|
|
||||||
demos = [True]
|
|
||||||
|
|
||||||
langs = lang or (map(lambda x: x[0], sock2.list_lang()) + ['en_US'])
|
|
||||||
|
|
||||||
def wait(id):
|
|
||||||
progress=0.0
|
|
||||||
while not progress==1.0:
|
|
||||||
time.sleep(3)
|
|
||||||
progress,users = sock2.get_progress(admin_passwd, id)
|
|
||||||
time.sleep(3)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def wizard_run(wizname, fieldvalues=None, endstate='end'):
|
|
||||||
if fieldvalues is None:
|
|
||||||
fieldvalues = {}
|
|
||||||
wiz_id = sock4.create(dbname, uid, 'admin', wizname)
|
|
||||||
state = 'init'
|
|
||||||
datas = {'form':{}}
|
|
||||||
while state!=endstate:
|
|
||||||
res = sock4.execute(dbname, uid, 'admin', wiz_id, datas, state, {})
|
|
||||||
if 'datas' in res:
|
|
||||||
datas['form'].update( res['datas'] )
|
|
||||||
if res['type']=='form':
|
|
||||||
for field in res['fields'].keys():
|
|
||||||
datas['form'][field] = res['fields'][field].get('value', False)
|
|
||||||
state = res['state'][-1][0]
|
|
||||||
datas['form'].update(fieldvalues)
|
|
||||||
elif res['type']=='state':
|
|
||||||
state = res['state']
|
|
||||||
return True
|
|
||||||
|
|
||||||
for demo in demos:
|
|
||||||
for l10n in l10n_charts:
|
|
||||||
print 'Testing localisation', l10n, '...'
|
|
||||||
for prof in profiles:
|
|
||||||
print '\tTesting profile', prof, '...'
|
|
||||||
id = sock2.create(admin_passwd, dbname, demo, lang)
|
|
||||||
wait(id)
|
|
||||||
uid = sock3.login(dbname, 'admin','admin')
|
|
||||||
|
|
||||||
idprof = sock.execute(dbname, uid, 'admin', 'ir.module.module', 'search', [('name','=',prof)])
|
|
||||||
if l10n:
|
|
||||||
idl10n = sock.execute(dbname, uid, 'admin', 'ir.module.module', 'search', [('name','=',l10n)])
|
|
||||||
else:
|
|
||||||
idl10n = [-1]
|
|
||||||
wizard_run('base_setup.base_setup', {
|
|
||||||
'profile': idprof[0],
|
|
||||||
'charts': idl10n[0],
|
|
||||||
}, 'menu')
|
|
||||||
for lang in langs:
|
|
||||||
print '\t\tTesting Language', lang, '...'
|
|
||||||
wizard_run('module.lang.install', {'lang': lang})
|
|
||||||
|
|
||||||
ok = False
|
|
||||||
range = 4
|
|
||||||
while (not ok) and range:
|
|
||||||
try:
|
|
||||||
time.sleep(2)
|
|
||||||
id = sock2.drop(admin_passwd, dbname)
|
|
||||||
ok = True
|
|
||||||
except:
|
|
||||||
range -= 1
|
|
||||||
time.sleep(2)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
|
|
||||||
SELECT model, res_id, module FROM ir_model_data
|
|
||||||
WHERE model = 'ir.actions.act_window'
|
|
||||||
AND NOT EXISTS (SELECT 1 FROM ir_act_window WHERE id = ir_model_data.res_id);
|
|
||||||
|
|
||||||
|
|
||||||
SELECT model, res_id, module FROM ir_model_data
|
|
||||||
WHERE model = 'ir.ui.menu'
|
|
||||||
AND NOT EXISTS (SELECT 1 FROM ir_ui_menu WHERE id = ir_model_data.res_id);
|
|
||||||
|
|
||||||
SELECT model, res_id, module FROM ir_model_data
|
|
||||||
WHERE model = 'ir.ui.view'
|
|
||||||
AND NOT EXISTS (SELECT 1 FROM ir_ui_view WHERE id = ir_model_data.res_id);
|
|
||||||
|
|
||||||
DONT DELETE FROM ir_model_data
|
|
||||||
WHERE model = 'ir.actions.act_window'
|
|
||||||
AND NOT EXISTS (SELECT 1 FROM ir_act_window WHERE id = ir_model_data.res_id);
|
|
||||||
|
|
||||||
DONT DELETE FROM ir_model_data
|
|
||||||
WHERE model = 'ir.ui.menu'
|
|
||||||
AND NOT EXISTS (SELECT 1 FROM ir_ui_menu WHERE id = ir_model_data.res_id);
|
|
||||||
-- Other cleanups:
|
|
||||||
-- DELETE from ir_model_data where module = 'audittrail' AND model = 'ir.ui.view' AND NOT EXISTS( SELECT 1 FROM ir_ui_view WHERE ir_ui_view.id = ir_model_data.res_id);
|
|
||||||
-- DELETE from ir_model_data where module = 'audittrail' AND model = 'ir.ui.menu' AND NOT EXISTS( SELECT 1 FROM ir_ui_menu WHERE id = ir_model_data.res_id);
|
|
|
@ -1,32 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Copyright (c) 2004-2009 TINY SPRL. (http://tiny.be)
|
|
||||||
#
|
|
||||||
# $Id$
|
|
||||||
#
|
|
||||||
# WARNING: This program as such is intended to be used by professional
|
|
||||||
# programmers who take the whole responsability of assessing all potential
|
|
||||||
# consequences resulting from its eventual inadequacies and bugs
|
|
||||||
# End users who are looking for a ready-to-use solution with commercial
|
|
||||||
# garantees and support are strongly adviced to contact a Free Software
|
|
||||||
# Service Company
|
|
||||||
#
|
|
||||||
# This program is Free Software; you can redistribute it and/or
|
|
||||||
# modify it under the terms of the GNU General Public License
|
|
||||||
# as published by the Free Software Foundation; either version 2
|
|
||||||
# of the License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
`dirname $0`/module_graph.py $@ | dot -Tpng -o > module_graph.png
|
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# ADMIN_PASSWD='admin'
|
|
||||||
method_1() {
|
|
||||||
cat '-' << EOF
|
|
||||||
<xml>
|
|
||||||
<methodCall>
|
|
||||||
<methodName>get_stats</methodName>
|
|
||||||
<params>
|
|
||||||
</params>
|
|
||||||
</methodCall>
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
LEVEL=10
|
|
||||||
|
|
||||||
if [ -n "$1" ] ; then LEVEL=$1 ; fi
|
|
||||||
|
|
||||||
method_1 $LEVEL | POST -c 'text/xml' http://localhost:8069/xmlrpc/common
|
|
||||||
#eof
|
|
|
@ -1,16 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# ADMIN_PASSWD='admin'
|
|
||||||
method_1() {
|
|
||||||
cat '-' << EOF
|
|
||||||
<xml>
|
|
||||||
<methodCall>
|
|
||||||
<methodName>list_http_services</methodName>
|
|
||||||
<params>
|
|
||||||
</params>
|
|
||||||
</methodCall>
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
method_1 | POST -c 'text/xml' http://localhost:8069/xmlrpc/common
|
|
||||||
#eof
|
|
|
@ -1,35 +0,0 @@
|
||||||
This document describes the steps to follow to migrate from a version 3.3.0 of Tiny ERP server to a version 3.4.0
|
|
||||||
|
|
||||||
Warning: the migration scripts involved in this migration are only meant for
|
|
||||||
a standard Tiny ERP installation. It might not work or even break some data
|
|
||||||
if you added or modified some code to the default Tiny ERP distribution.
|
|
||||||
|
|
||||||
To migrate a 3.3.0 server to version 3.4.0 you should:
|
|
||||||
|
|
||||||
- stop Tiny ERP server 3.3.0
|
|
||||||
|
|
||||||
- backup your database
|
|
||||||
For example: pg_dump terp330 > backup330.sql
|
|
||||||
|
|
||||||
- run the pre.py script (located in this directory)
|
|
||||||
You might need to pass it some optional arguments so that it can connect
|
|
||||||
to the database.
|
|
||||||
|
|
||||||
For example: python pre.py -d terp330
|
|
||||||
|
|
||||||
- run TinyERP server 3.4.0 with "-u all" in the parameters
|
|
||||||
For example: ./tinyerp-server.py -d terp330 -u all
|
|
||||||
|
|
||||||
- stop TinyERP server 3.4.0
|
|
||||||
|
|
||||||
- run the post.py script (located in this directory)
|
|
||||||
|
|
||||||
You might need to pass it some optional arguments so that it can connect
|
|
||||||
to the database.
|
|
||||||
|
|
||||||
For example: python post.py -d terp330
|
|
||||||
|
|
||||||
- run TinyERP server 3.4.0 again with "-u all" in the parameters
|
|
||||||
For example: ./tinyerp-server.py -d terp330 -u all
|
|
||||||
|
|
||||||
- you are ready to work with the new version.
|
|
|
@ -1,146 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# OpenERP, Open Source Management Solution
|
|
||||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as
|
|
||||||
# published by the Free Software Foundation, either version 3 of the
|
|
||||||
# License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
__author__ = 'Gaetan de Menten, <ged@tiny.be>'
|
|
||||||
__version__ = '0.1.0'
|
|
||||||
|
|
||||||
import psycopg
|
|
||||||
import optparse
|
|
||||||
import ConfigParser
|
|
||||||
|
|
||||||
# -----
|
|
||||||
|
|
||||||
parser = optparse.OptionParser(version="Tiny ERP server migration script " + __version__)
|
|
||||||
|
|
||||||
parser.add_option("-c", "--config", dest="config", help="specify path to Tiny ERP config file")
|
|
||||||
|
|
||||||
group = optparse.OptionGroup(parser, "Database related options")
|
|
||||||
group.add_option("--db_host", dest="db_host", help="specify the database host")
|
|
||||||
group.add_option("--db_port", dest="db_port", help="specify the database port")
|
|
||||||
group.add_option("-d", "--database", dest="db_name", help="specify the database name")
|
|
||||||
group.add_option("-r", "--db_user", dest="db_user", help="specify the database user name")
|
|
||||||
group.add_option("-w", "--db_password", dest="db_password", help="specify the database password")
|
|
||||||
parser.add_option_group(group)
|
|
||||||
|
|
||||||
options = optparse.Values()
|
|
||||||
options.db_name = 'terp' # default value
|
|
||||||
parser.parse_args(values=options)
|
|
||||||
|
|
||||||
if hasattr(options, 'config'):
|
|
||||||
configparser = ConfigParser.ConfigParser()
|
|
||||||
configparser.read([options.config])
|
|
||||||
for name, value in configparser.items('options'):
|
|
||||||
if not (hasattr(options, name) and getattr(options, name)):
|
|
||||||
if value in ('true', 'True'):
|
|
||||||
value = True
|
|
||||||
if value in ('false', 'False'):
|
|
||||||
value = False
|
|
||||||
setattr(options, name, value)
|
|
||||||
|
|
||||||
# -----
|
|
||||||
|
|
||||||
host = hasattr(options, 'db_host') and "host=%s" % options.db_host or ''
|
|
||||||
port = hasattr(options, 'db_port') and "port=%s" % options.db_port or ''
|
|
||||||
name = "dbname=%s" % options.db_name
|
|
||||||
user = hasattr(options, 'db_user') and "user=%s" % options.db_user or ''
|
|
||||||
password = hasattr(options, 'db_password') and "password=%s" % options.db_password or ''
|
|
||||||
|
|
||||||
db = psycopg.connect('%s %s %s %s %s' % (host, port, name, user, password), serialize=0)
|
|
||||||
cr = db.cursor()
|
|
||||||
|
|
||||||
# ------------------------------------------- #
|
|
||||||
# convert partner payment terms to properties #
|
|
||||||
# ------------------------------------------- #
|
|
||||||
|
|
||||||
# setup
|
|
||||||
|
|
||||||
cr.execute("select id from ir_model_fields where name='property_payment_term' and model='res.partner'")
|
|
||||||
fields_id = cr.fetchone()[0]
|
|
||||||
|
|
||||||
cr.execute("select company_id from res_users where company_id is not null limit 1")
|
|
||||||
company_id = cr.fetchone()[0]
|
|
||||||
|
|
||||||
# get partners
|
|
||||||
cr.execute("SELECT c.relname FROM pg_class c, pg_attribute a WHERE c.relname='res_partner' AND a.attname='payment_term' AND c.oid=a.attrelid")
|
|
||||||
partners=[]
|
|
||||||
drop_payment_term=False
|
|
||||||
if cr.rowcount:
|
|
||||||
drop_payment_term=True
|
|
||||||
cr.execute("select id, payment_term from res_partner where payment_term is not null")
|
|
||||||
partners = cr.dictfetchall()
|
|
||||||
|
|
||||||
# loop over them
|
|
||||||
|
|
||||||
for partner in partners:
|
|
||||||
value = 'account.payment.term,%d' % partner['payment_term']
|
|
||||||
res_id = 'res.partner,%d' % partner['id']
|
|
||||||
cr.execute(
|
|
||||||
"insert into ir_property(name, value, res_id, company_id, fields_id) "\
|
|
||||||
"values(%s, %s, %s, %d, %d)",
|
|
||||||
('property_payment_term', value, res_id, company_id, fields_id))
|
|
||||||
|
|
||||||
# remove the field
|
|
||||||
if drop_payment_term:
|
|
||||||
cr.execute("alter table res_partner drop column payment_term")
|
|
||||||
cr.execute("delete from ir_model_fields where model = 'res.partner' and name = 'payment_term'")
|
|
||||||
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
# ------------------------ #
|
|
||||||
# remove duplicate reports #
|
|
||||||
# ------------------------ #
|
|
||||||
|
|
||||||
cr.execute("select model, report_name from ir_act_report_xml group by model, report_name having count(*)>1")
|
|
||||||
reports_wh_duplicates = cr.dictfetchall()
|
|
||||||
|
|
||||||
cr.execute("select res_id from ir_model_data where model='ir.actions.report.xml'")
|
|
||||||
registered_reports = cr.fetchall()
|
|
||||||
reg_reports_ids = ','.join([str(id) for (id,) in registered_reports])
|
|
||||||
|
|
||||||
for report in reports_wh_duplicates:
|
|
||||||
cr.execute("select id from ir_act_report_xml where model=%s and report_name=%s and id not in ("+reg_reports_ids+")", (report['model'], report['report_name']))
|
|
||||||
(id,) = cr.fetchone()
|
|
||||||
cr.execute("delete from ir_act_report_xml where id=%d", (id,))
|
|
||||||
cr.execute("delete from ir_values where value='ir.actions.report.xml,%d'", (id,))
|
|
||||||
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
# ------------------------------------- #
|
|
||||||
# remove duplicate workflow transitions #
|
|
||||||
# ------------------------------------- #
|
|
||||||
|
|
||||||
# this removes all transitions which are not registered in ir_model_data
|
|
||||||
|
|
||||||
cr.execute("delete from wkf_transition where id not in (select res_id from ir_model_data where model='workflow.transition')")
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
# -------------------------------- #
|
|
||||||
# remove bad "default" menu action #
|
|
||||||
# -------------------------------- #
|
|
||||||
|
|
||||||
cr.execute("delete from ir_values where key='action' and model='ir.ui.menu' and res_id is null")
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
cr.close()
|
|
||||||
|
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
||||||
|
|
|
@ -1,112 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# OpenERP, Open Source Management Solution
|
|
||||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as
|
|
||||||
# published by the Free Software Foundation, either version 3 of the
|
|
||||||
# License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
__author__ = 'Gaetan de Menten, <ged@tiny.be>'
|
|
||||||
__version__ = '0.1.0'
|
|
||||||
|
|
||||||
import psycopg
|
|
||||||
import optparse
|
|
||||||
import ConfigParser
|
|
||||||
|
|
||||||
# -----
|
|
||||||
|
|
||||||
parser = optparse.OptionParser(version="Tiny ERP server migration script " + __version__)
|
|
||||||
|
|
||||||
parser.add_option("-c", "--config", dest="config", help="specify path to Tiny ERP config file")
|
|
||||||
|
|
||||||
group = optparse.OptionGroup(parser, "Database related options")
|
|
||||||
group.add_option("--db_host", dest="db_host", help="specify the database host")
|
|
||||||
group.add_option("--db_port", dest="db_port", help="specify the database port")
|
|
||||||
group.add_option("-d", "--database", dest="db_name", help="specify the database name")
|
|
||||||
group.add_option("-r", "--db_user", dest="db_user", help="specify the database user name")
|
|
||||||
group.add_option("-w", "--db_password", dest="db_password", help="specify the database password")
|
|
||||||
parser.add_option_group(group)
|
|
||||||
|
|
||||||
options = optparse.Values()
|
|
||||||
options.db_name = 'terp' # default value
|
|
||||||
parser.parse_args(values=options)
|
|
||||||
|
|
||||||
if hasattr(options, 'config'):
|
|
||||||
configparser = ConfigParser.ConfigParser()
|
|
||||||
configparser.read([options.config])
|
|
||||||
for name, value in configparser.items('options'):
|
|
||||||
if not (hasattr(options, name) and getattr(options, name)):
|
|
||||||
if value in ('true', 'True'):
|
|
||||||
value = True
|
|
||||||
if value in ('false', 'False'):
|
|
||||||
value = False
|
|
||||||
setattr(options, name, value)
|
|
||||||
|
|
||||||
# -----
|
|
||||||
|
|
||||||
host = hasattr(options, 'db_host') and "host=%s" % options.db_host or ''
|
|
||||||
port = hasattr(options, 'db_port') and "port=%s" % options.db_port or ''
|
|
||||||
name = "dbname=%s" % options.db_name
|
|
||||||
user = hasattr(options, 'db_user') and "user=%s" % options.db_user or ''
|
|
||||||
password = hasattr(options, 'db_password') and "password=%s" % options.db_password or ''
|
|
||||||
|
|
||||||
db = psycopg.connect('%s %s %s %s %s' % (host, port, name, user, password), serialize=0)
|
|
||||||
cr = db.cursor()
|
|
||||||
|
|
||||||
# ------------------------- #
|
|
||||||
# change some columns types #
|
|
||||||
# ------------------------- #
|
|
||||||
|
|
||||||
def change_column(cr, table, column, new_type, copy):
|
|
||||||
commands = [
|
|
||||||
"ALTER TABLE %s RENAME COLUMN %s TO temp_column" % (table, column),
|
|
||||||
"ALTER TABLE %s ADD COLUMN %s %s" % (table, column, new_type),
|
|
||||||
"ALTER TABLE %s DROP COLUMN temp_column" % table
|
|
||||||
]
|
|
||||||
if copy:
|
|
||||||
commands.insert(
|
|
||||||
2,
|
|
||||||
"UPDATE %s SET %s=temp_column::%s" % (table, column, new_type))
|
|
||||||
|
|
||||||
for command in commands:
|
|
||||||
cr.execute(command)
|
|
||||||
|
|
||||||
change_column(cr, 'account_account_type', 'code_from', 'varchar(10)', False)
|
|
||||||
change_column(cr, 'account_account_type', 'code_to', 'varchar(10)', False)
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
# ----------------------------------------------------- #
|
|
||||||
# add some fields (which cannot be added automatically) #
|
|
||||||
# ----------------------------------------------------- #
|
|
||||||
|
|
||||||
for line in (
|
|
||||||
"alter table ir_model_fields add group_name varchar(64)",
|
|
||||||
"alter table ir_model_fields add view_load boolean",
|
|
||||||
"alter table ir_model_fields alter group_name set default ''",
|
|
||||||
"alter table ir_model_fields alter view_load set default False",
|
|
||||||
"delete from ir_values where value like '%,False'",
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
cr.execute(line)
|
|
||||||
except psycopg.ProgrammingError, e:
|
|
||||||
cr.commit()
|
|
||||||
print e
|
|
||||||
|
|
||||||
cr.commit()
|
|
||||||
cr.close()
|
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
This document describes the steps to follow to migrate from a version 3.4.0 of Tiny ERP server to a version 4.0.0
|
|
||||||
|
|
||||||
Warning: the migration scripts involved in this migration are only meant for
|
|
||||||
a standard Tiny ERP installation. It might not work or even break some data
|
|
||||||
if you added or modified some code to the default Tiny ERP distribution.
|
|
||||||
|
|
||||||
To migrate a 3.4.0 server to version 4.0.0 you should:
|
|
||||||
|
|
||||||
- stop Tiny ERP server 3.4.0
|
|
||||||
|
|
||||||
- backup your database
|
|
||||||
For example: pg_dump terp340 > backup340.sql
|
|
||||||
|
|
||||||
- run the pre.py script (located in this directory)
|
|
||||||
You might need to pass it some optional arguments so that it can connect
|
|
||||||
to the database.
|
|
||||||
|
|
||||||
For example: python pre.py -d terp340
|
|
||||||
|
|
||||||
- run TinyERP server 4.0.0 with "-d terp340 -u all" in the parameters
|
|
||||||
For example: ./tinyerp-server.py -d terp340 -u all
|
|
||||||
|
|
||||||
- stop TinyERP server 4.0.0
|
|
||||||
|
|
||||||
- run the post.py script (located in this directory)
|
|
||||||
You might need to pass it some optional arguments so that it can connect
|
|
||||||
to the database.
|
|
||||||
|
|
||||||
- you are ready to work with the new version.
|
|
|
@ -1,87 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# OpenERP, Open Source Management Solution
|
|
||||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as
|
|
||||||
# published by the Free Software Foundation, either version 3 of the
|
|
||||||
# License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
__author__ = 'Gaetan de Menten, <ged@tiny.be>'
|
|
||||||
__version__ = '0.1.0'
|
|
||||||
|
|
||||||
import psycopg
|
|
||||||
import optparse
|
|
||||||
import ConfigParser
|
|
||||||
|
|
||||||
# -----
|
|
||||||
|
|
||||||
parser = optparse.OptionParser(version="Tiny ERP server migration script " + __version__)
|
|
||||||
|
|
||||||
parser.add_option("-c", "--config", dest="config", help="specify path to Tiny ERP config file")
|
|
||||||
|
|
||||||
group = optparse.OptionGroup(parser, "Database related options")
|
|
||||||
group.add_option("--db_host", dest="db_host", help="specify the database host")
|
|
||||||
group.add_option("--db_port", dest="db_port", help="specify the database port")
|
|
||||||
group.add_option("-d", "--database", dest="db_name", help="specify the database name")
|
|
||||||
group.add_option("-r", "--db_user", dest="db_user", help="specify the database user name")
|
|
||||||
group.add_option("-w", "--db_password", dest="db_password", help="specify the database password")
|
|
||||||
parser.add_option_group(group)
|
|
||||||
|
|
||||||
options = optparse.Values()
|
|
||||||
options.db_name = 'terp' # default value
|
|
||||||
parser.parse_args(values=options)
|
|
||||||
|
|
||||||
if hasattr(options, 'config'):
|
|
||||||
configparser = ConfigParser.ConfigParser()
|
|
||||||
configparser.read([options.config])
|
|
||||||
for name, value in configparser.items('options'):
|
|
||||||
if not (hasattr(options, name) and getattr(options, name)):
|
|
||||||
if value in ('true', 'True'):
|
|
||||||
value = True
|
|
||||||
if value in ('false', 'False'):
|
|
||||||
value = False
|
|
||||||
setattr(options, name, value)
|
|
||||||
|
|
||||||
# -----
|
|
||||||
|
|
||||||
host = hasattr(options, 'db_host') and "host=%s" % options.db_host or ''
|
|
||||||
port = hasattr(options, 'db_port') and "port=%s" % options.db_port or ''
|
|
||||||
name = "dbname=%s" % options.db_name
|
|
||||||
user = hasattr(options, 'db_user') and "user=%s" % options.db_user or ''
|
|
||||||
password = hasattr(options, 'db_password') and "password=%s" % options.db_password or ''
|
|
||||||
|
|
||||||
db = psycopg.connect('%s %s %s %s %s' % (host, port, name, user, password), serialize=0)
|
|
||||||
cr = db.cursor()
|
|
||||||
|
|
||||||
# --------------- #
|
|
||||||
# remove old menu #
|
|
||||||
# --------------- #
|
|
||||||
|
|
||||||
cr.execute("delete from ir_ui_menu where (id not in (select parent_id from ir_ui_menu where parent_id is not null)) and (id not in (select res_id from ir_values where model='ir.ui.menu'))")
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
# --------------- #
|
|
||||||
# remove ir_value #
|
|
||||||
# --------------- #
|
|
||||||
|
|
||||||
cr.execute("delete from ir_values where model = 'ir.ui.menu' and res_id is null")
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
cr.close()
|
|
||||||
|
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
||||||
|
|
|
@ -1,116 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# OpenERP, Open Source Management Solution
|
|
||||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as
|
|
||||||
# published by the Free Software Foundation, either version 3 of the
|
|
||||||
# License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
__author__ = 'Gaetan de Menten, <ged@tiny.be>'
|
|
||||||
__version__ = '0.1.0'
|
|
||||||
|
|
||||||
import psycopg
|
|
||||||
import optparse
|
|
||||||
import ConfigParser
|
|
||||||
|
|
||||||
# -----
|
|
||||||
|
|
||||||
parser = optparse.OptionParser(version="Tiny ERP server migration script " + __version__)
|
|
||||||
|
|
||||||
parser.add_option("-c", "--config", dest="config", help="specify path to Tiny ERP config file")
|
|
||||||
|
|
||||||
group = optparse.OptionGroup(parser, "Database related options")
|
|
||||||
group.add_option("--db_host", dest="db_host", help="specify the database host")
|
|
||||||
group.add_option("--db_port", dest="db_port", help="specify the database port")
|
|
||||||
group.add_option("-d", "--database", dest="db_name", help="specify the database name")
|
|
||||||
group.add_option("-r", "--db_user", dest="db_user", help="specify the database user name")
|
|
||||||
group.add_option("-w", "--db_password", dest="db_password", help="specify the database password")
|
|
||||||
parser.add_option_group(group)
|
|
||||||
|
|
||||||
options = optparse.Values()
|
|
||||||
options.db_name = 'terp' # default value
|
|
||||||
parser.parse_args(values=options)
|
|
||||||
|
|
||||||
if hasattr(options, 'config'):
|
|
||||||
configparser = ConfigParser.ConfigParser()
|
|
||||||
configparser.read([options.config])
|
|
||||||
for name, value in configparser.items('options'):
|
|
||||||
if not (hasattr(options, name) and getattr(options, name)):
|
|
||||||
if value in ('true', 'True'):
|
|
||||||
value = True
|
|
||||||
if value in ('false', 'False'):
|
|
||||||
value = False
|
|
||||||
setattr(options, name, value)
|
|
||||||
|
|
||||||
# -----
|
|
||||||
|
|
||||||
host = hasattr(options, 'db_host') and "host=%s" % options.db_host or ''
|
|
||||||
port = hasattr(options, 'db_port') and "port=%s" % options.db_port or ''
|
|
||||||
name = "dbname=%s" % options.db_name
|
|
||||||
user = hasattr(options, 'db_user') and "user=%s" % options.db_user or ''
|
|
||||||
password = hasattr(options, 'db_password') and "password=%s" % options.db_password or ''
|
|
||||||
|
|
||||||
db = psycopg.connect('%s %s %s %s %s' % (host, port, name, user, password), serialize=0)
|
|
||||||
cr = db.cursor()
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------- #
|
|
||||||
# move user id from hr_analytic_timesheet to account_analytic_line #
|
|
||||||
# ---------------------------------------------------------------- #
|
|
||||||
|
|
||||||
cr.execute("UPDATE account_analytic_line SET user_id = hr_analytic_timesheet.user_id FROM hr_analytic_timesheet WHERE hr_analytic_timesheet.line_id = account_analytic_line.id")
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
# --------------- #
|
|
||||||
# remove old menu #
|
|
||||||
# --------------- #
|
|
||||||
|
|
||||||
while True:
|
|
||||||
cr.execute("select id from ir_ui_menu where (id not in (select parent_id from ir_ui_menu where parent_id is not null)) and (id not in (select res_id from ir_values where model='ir.ui.menu'))")
|
|
||||||
if not cr.rowcount:
|
|
||||||
break
|
|
||||||
cr.execute("delete from ir_ui_menu where (id not in (select parent_id from ir_ui_menu where parent_id is not null)) and (id not in (select res_id from ir_values where model='ir.ui.menu'))")
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
# ----------------------------------------- #
|
|
||||||
# add default value for discount in invoice #
|
|
||||||
# ----------------------------------------- #
|
|
||||||
|
|
||||||
cr.execute("update account_invoice_line set discount=0.0 where discount is null;")
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------- #
|
|
||||||
# update constraint account_invoice_line_uos_id_fkey on account_invoice_line #
|
|
||||||
# -------------------------------------------------------------------------- #
|
|
||||||
|
|
||||||
cr.execute("ALTER TABLE account_invoice_line DROP CONSTRAINT account_invoice_line_uos_id_fkey")
|
|
||||||
cr.execute("ALTER TABLE account_invoice_line ADD FOREIGN KEY (uos_id) REFERENCES product_uom(id) ON DELETE SET NULL")
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
print """
|
|
||||||
WARNING: account_uos has been replaced by product_uom.
|
|
||||||
It is not possible to migrate the data automatically so you need to create the old account_uos in the new product_uom.
|
|
||||||
And then update the field uos_id of the table account_invoice to match the new id of product_uom.
|
|
||||||
|
|
||||||
EXAMPLE:
|
|
||||||
UPDATE account_invoice SET uos_id = new_id WHERE uos_id = old_id;
|
|
||||||
"""
|
|
||||||
|
|
||||||
cr.close()
|
|
||||||
|
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
||||||
|
|
|
@ -1,145 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# OpenERP, Open Source Management Solution
|
|
||||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as
|
|
||||||
# published by the Free Software Foundation, either version 3 of the
|
|
||||||
# License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
__author__ = 'Gaetan de Menten, <ged@tiny.be>'
|
|
||||||
__version__ = '0.1.0'
|
|
||||||
|
|
||||||
import psycopg
|
|
||||||
import optparse
|
|
||||||
import ConfigParser
|
|
||||||
|
|
||||||
# -----
|
|
||||||
|
|
||||||
parser = optparse.OptionParser(version="Tiny ERP server migration script " + __version__)
|
|
||||||
|
|
||||||
parser.add_option("-c", "--config", dest="config", help="specify path to Tiny ERP config file")
|
|
||||||
|
|
||||||
group = optparse.OptionGroup(parser, "Database related options")
|
|
||||||
group.add_option("--db_host", dest="db_host", help="specify the database host")
|
|
||||||
group.add_option("--db_port", dest="db_port", help="specify the database port")
|
|
||||||
group.add_option("-d", "--database", dest="db_name", help="specify the database name")
|
|
||||||
group.add_option("-r", "--db_user", dest="db_user", help="specify the database user name")
|
|
||||||
group.add_option("-w", "--db_password", dest="db_password", help="specify the database password")
|
|
||||||
parser.add_option_group(group)
|
|
||||||
|
|
||||||
options = optparse.Values()
|
|
||||||
options.db_name = 'terp' # default value
|
|
||||||
parser.parse_args(values=options)
|
|
||||||
|
|
||||||
if hasattr(options, 'config'):
|
|
||||||
configparser = ConfigParser.ConfigParser()
|
|
||||||
configparser.read([options.config])
|
|
||||||
for name, value in configparser.items('options'):
|
|
||||||
if not (hasattr(options, name) and getattr(options, name)):
|
|
||||||
if value in ('true', 'True'):
|
|
||||||
value = True
|
|
||||||
if value in ('false', 'False'):
|
|
||||||
value = False
|
|
||||||
setattr(options, name, value)
|
|
||||||
|
|
||||||
# -----
|
|
||||||
|
|
||||||
host = hasattr(options, 'db_host') and "host=%s" % options.db_host or ''
|
|
||||||
port = hasattr(options, 'db_port') and "port=%s" % options.db_port or ''
|
|
||||||
name = "dbname=%s" % options.db_name
|
|
||||||
user = hasattr(options, 'db_user') and "user=%s" % options.db_user or ''
|
|
||||||
password = hasattr(options, 'db_password') and "password=%s" % options.db_password or ''
|
|
||||||
|
|
||||||
db = psycopg.connect('%s %s %s %s %s' % (host, port, name, user, password), serialize=0)
|
|
||||||
cr = db.cursor()
|
|
||||||
|
|
||||||
# ------------------------- #
|
|
||||||
# change some columns types #
|
|
||||||
# ------------------------- #
|
|
||||||
|
|
||||||
def change_column(cr, table, column, new_type, copy):
|
|
||||||
commands = [
|
|
||||||
"ALTER TABLE %s RENAME COLUMN %s TO temp_column" % (table, column),
|
|
||||||
"ALTER TABLE %s ADD COLUMN %s %s" % (table, column, new_type),
|
|
||||||
"ALTER TABLE %s DROP COLUMN temp_column" % table
|
|
||||||
]
|
|
||||||
if copy:
|
|
||||||
commands.insert(
|
|
||||||
2,
|
|
||||||
"UPDATE %s SET %s=temp_column::%s" % (table, column, new_type))
|
|
||||||
|
|
||||||
for command in commands:
|
|
||||||
cr.execute(command)
|
|
||||||
|
|
||||||
#change_column(cr, 'crm_case', 'date_closed', 'timestamp', True)
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
# -------------------- #
|
|
||||||
# add module if needed #
|
|
||||||
# -------------------- #
|
|
||||||
|
|
||||||
cr.execute("SELECT name FROM ir_module_module")
|
|
||||||
if not cr.rowcount:
|
|
||||||
for module in set(['base', 'marketing', 'subscription', 'account', 'base_partner_relation', 'audittrail', 'account_followup', 'product', 'hr', 'l10n_simple', 'crm', 'stock', 'hr_timesheet', 'purchase', 'report_purchase', 'mrp', 'sale', 'report_sale', 'delivery', 'project', 'sale_crm', 'hr_timesheet_project', 'scrum', 'report_project',
|
|
||||||
'account_followup',
|
|
||||||
'account',
|
|
||||||
'audittrail',
|
|
||||||
'base_partner_relation',
|
|
||||||
'crm',
|
|
||||||
'delivery',
|
|
||||||
'edi',
|
|
||||||
'hr_evaluation',
|
|
||||||
'hr_expense',
|
|
||||||
'hr',
|
|
||||||
'hr_timesheet_invoice',
|
|
||||||
'hr_timesheet_project',
|
|
||||||
'hr_timesheet',
|
|
||||||
'l10n_simple',
|
|
||||||
'marketing',
|
|
||||||
'mrp',
|
|
||||||
'network',
|
|
||||||
'product',
|
|
||||||
'project',
|
|
||||||
'purchase',
|
|
||||||
'report_crm',
|
|
||||||
'report_project',
|
|
||||||
'report_purchase',
|
|
||||||
'report_sale',
|
|
||||||
'sale_crm',
|
|
||||||
'sale',
|
|
||||||
'sandwich',
|
|
||||||
'scrum',
|
|
||||||
'stock']):
|
|
||||||
cr.execute("INSERT INTO ir_module_module (name, state) VALUES ('%s', 'installed')" % module)
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------------- #
|
|
||||||
# add some fields (which cannot be added automatically) #
|
|
||||||
# ----------------------------------------------------- #
|
|
||||||
|
|
||||||
for line in (
|
|
||||||
"ALTER TABLE ir_module_module ADD demo BOOLEAN DEFAULT False",
|
|
||||||
"delete from ir_values where value like '%,False'",
|
|
||||||
"""UPDATE ir_ui_view set arch='<?xml version="1.0"?><tree string="Menu" toolbar="1"><field icon="icon" name="name"/></tree>' where name='ir.ui.menu.tree' and type='tree' and field_parent='child_id'""",
|
|
||||||
):
|
|
||||||
cr.execute(line)
|
|
||||||
|
|
||||||
cr.commit()
|
|
||||||
cr.close()
|
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
||||||
|
|
|
@ -1,127 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# OpenERP, Open Source Management Solution
|
|
||||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as
|
|
||||||
# published by the Free Software Foundation, either version 3 of the
|
|
||||||
# License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
__author__ = 'Gaetan de Menten, <ged@tiny.be>'
|
|
||||||
__version__ = '0.1.0'
|
|
||||||
|
|
||||||
import psycopg
|
|
||||||
import optparse
|
|
||||||
import ConfigParser
|
|
||||||
|
|
||||||
# -----
|
|
||||||
|
|
||||||
parser = optparse.OptionParser(version="Tiny ERP server migration script " + __version__)
|
|
||||||
|
|
||||||
parser.add_option("-c", "--config", dest="config", help="specify path to Tiny ERP config file")
|
|
||||||
|
|
||||||
group = optparse.OptionGroup(parser, "Database related options")
|
|
||||||
group.add_option("--db_host", dest="db_host", help="specify the database host")
|
|
||||||
group.add_option("--db_port", dest="db_port", help="specify the database port")
|
|
||||||
group.add_option("-d", "--database", dest="db_name", help="specify the database name")
|
|
||||||
group.add_option("-r", "--db_user", dest="db_user", help="specify the database user name")
|
|
||||||
group.add_option("-w", "--db_password", dest="db_password", help="specify the database password")
|
|
||||||
parser.add_option_group(group)
|
|
||||||
|
|
||||||
options = optparse.Values()
|
|
||||||
options.db_name = 'terp' # default value
|
|
||||||
parser.parse_args(values=options)
|
|
||||||
|
|
||||||
if hasattr(options, 'config'):
|
|
||||||
configparser = ConfigParser.ConfigParser()
|
|
||||||
configparser.read([options.config])
|
|
||||||
for name, value in configparser.items('options'):
|
|
||||||
if not (hasattr(options, name) and getattr(options, name)):
|
|
||||||
if value in ('true', 'True'):
|
|
||||||
value = True
|
|
||||||
if value in ('false', 'False'):
|
|
||||||
value = False
|
|
||||||
setattr(options, name, value)
|
|
||||||
|
|
||||||
# -----
|
|
||||||
|
|
||||||
host = hasattr(options, 'db_host') and "host=%s" % options.db_host or ''
|
|
||||||
port = hasattr(options, 'db_port') and "port=%s" % options.db_port or ''
|
|
||||||
name = "dbname=%s" % options.db_name
|
|
||||||
user = hasattr(options, 'db_user') and "user=%s" % options.db_user or ''
|
|
||||||
password = hasattr(options, 'db_password') and "password=%s" % options.db_password or ''
|
|
||||||
|
|
||||||
db = psycopg.connect('%s %s %s %s %s' % (host, port, name, user, password), serialize=0)
|
|
||||||
cr = db.cursor()
|
|
||||||
|
|
||||||
# ------------------------- #
|
|
||||||
# change some columns types #
|
|
||||||
# ------------------------- #
|
|
||||||
|
|
||||||
def change_column(cr, table, column, new_type, copy):
|
|
||||||
commands = [
|
|
||||||
"ALTER TABLE %s RENAME COLUMN %s TO temp_column" % (table, column),
|
|
||||||
"ALTER TABLE %s ADD COLUMN %s %s" % (table, column, new_type),
|
|
||||||
"ALTER TABLE %s DROP COLUMN temp_column" % table
|
|
||||||
]
|
|
||||||
if copy:
|
|
||||||
commands.insert(
|
|
||||||
2,
|
|
||||||
"UPDATE %s SET %s=temp_column::%s" % (table, column, new_type))
|
|
||||||
|
|
||||||
for command in commands:
|
|
||||||
cr.execute(command)
|
|
||||||
|
|
||||||
change_column(cr, 'crm_case', 'date_closed', 'timestamp', True)
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
# -------------------- #
|
|
||||||
# add module if needed #
|
|
||||||
# -------------------- #
|
|
||||||
|
|
||||||
cr.execute("SELECT name FROM ir_module_module")
|
|
||||||
if not cr.rowcount:
|
|
||||||
for module in ('base', 'marketing', 'subscription', 'account', 'base_partner_relation', 'audittrail', 'account_followup', 'product', 'hr', 'l10n_simple', 'crm', 'stock', 'hr_timesheet', 'purchase', 'report_purchase', 'mrp', 'sale', 'report_sale', 'delivery', 'project', 'sale_crm', 'hr_timesheet_project', 'scrum', 'report_project'):
|
|
||||||
cr.execute("INSERT INTO ir_module_module (name, state) VALUES ('%s', 'installed')" % module)
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
# --------------- #
|
|
||||||
# remove old menu #
|
|
||||||
# --------------- #
|
|
||||||
|
|
||||||
while True:
|
|
||||||
cr.execute("select id from ir_ui_menu where id not in (select parent_id from ir_ui_menu where parent_id is not null) and id not in (select res_id from ir_model_data where model='ir.ui.menu')")
|
|
||||||
if not cr.rowcount:
|
|
||||||
break
|
|
||||||
cr.execute("delete from ir_ui_menu where id not in (select parent_id from ir_ui_menu where parent_id is not null) and id not in (select res_id from ir_model_data where model='ir.ui.menu')")
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
# ----------------------------------------------------- #
|
|
||||||
# add some fields (which cannot be added automatically) #
|
|
||||||
# ----------------------------------------------------- #
|
|
||||||
|
|
||||||
for line in (
|
|
||||||
"ALTER TABLE ir_module_module ADD demo BOOLEAN",
|
|
||||||
"ALTER TABLE ir_module_module SET demo DEFAULT False",
|
|
||||||
"DELETE FROM ir_values WHERE VALUE LIKE '%,False'",
|
|
||||||
"""UPDATE ir_ui_view set arch='<?xml version="1.0"?><tree string="Menu" toolbar="1"><field icon="icon" name="name"/></tree>' where name='ir.ui.menu.tree' and type='tree' and field_parent='child_id'""",
|
|
||||||
):
|
|
||||||
cr.execute(line)
|
|
||||||
|
|
||||||
cr.commit()
|
|
||||||
cr.close()
|
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
||||||
|
|
|
@ -1,247 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# OpenERP, Open Source Management Solution
|
|
||||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as
|
|
||||||
# published by the Free Software Foundation, either version 3 of the
|
|
||||||
# License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
__author__ = 'Cédric Krier, <ced@tinyerp.com>'
|
|
||||||
__version__ = '0.1.0'
|
|
||||||
|
|
||||||
import psycopg
|
|
||||||
import optparse
|
|
||||||
import ConfigParser
|
|
||||||
|
|
||||||
# -----
|
|
||||||
|
|
||||||
parser = optparse.OptionParser(version="Tiny ERP server migration script " + __version__)
|
|
||||||
|
|
||||||
parser.add_option("-c", "--config", dest="config", help="specify path to Tiny ERP config file")
|
|
||||||
|
|
||||||
group = optparse.OptionGroup(parser, "Database related options")
|
|
||||||
group.add_option("--db_host", dest="db_host", help="specify the database host")
|
|
||||||
group.add_option("--db_port", dest="db_port", help="specify the database port")
|
|
||||||
group.add_option("-d", "--database", dest="db_name", help="specify the database name")
|
|
||||||
group.add_option("-r", "--db_user", dest="db_user", help="specify the database user name")
|
|
||||||
group.add_option("-w", "--db_password", dest="db_password", help="specify the database password")
|
|
||||||
parser.add_option_group(group)
|
|
||||||
|
|
||||||
options = optparse.Values()
|
|
||||||
options.db_name = 'terp' # default value
|
|
||||||
parser.parse_args(values=options)
|
|
||||||
|
|
||||||
if hasattr(options, 'config'):
|
|
||||||
configparser = ConfigParser.ConfigParser()
|
|
||||||
configparser.read([options.config])
|
|
||||||
for name, value in configparser.items('options'):
|
|
||||||
if not (hasattr(options, name) and getattr(options, name)):
|
|
||||||
if value in ('true', 'True'):
|
|
||||||
value = True
|
|
||||||
if value in ('false', 'False'):
|
|
||||||
value = False
|
|
||||||
setattr(options, name, value)
|
|
||||||
|
|
||||||
# -----
|
|
||||||
|
|
||||||
host = hasattr(options, 'db_host') and "host=%s" % options.db_host or ''
|
|
||||||
port = hasattr(options, 'db_port') and "port=%s" % options.db_port or ''
|
|
||||||
name = "dbname=%s" % options.db_name
|
|
||||||
user = hasattr(options, 'db_user') and "user=%s" % options.db_user or ''
|
|
||||||
password = hasattr(options, 'db_password') and "password=%s" % options.db_password or ''
|
|
||||||
|
|
||||||
db = psycopg.connect('%s %s %s %s %s' % (host, port, name, user, password), serialize=0)
|
|
||||||
cr = db.cursor()
|
|
||||||
|
|
||||||
# ------------------------ #
|
|
||||||
# change currency rounding #
|
|
||||||
# ------------------------ #
|
|
||||||
|
|
||||||
cr.execute("""SELECT c.relname,a.attname,a.attlen,a.atttypmod,a.attnotnull,a.atthasdef,t.typname,CASE WHEN a.attlen=-1 THEN a.atttypmod-4 ELSE a.attlen END as size FROM pg_class c,pg_attribute a,pg_type t WHERE c.relname='res_currency' AND a.attname='rounding' AND c.oid=a.attrelid AND a.atttypid=t.oid""")
|
|
||||||
res = cr.dictfetchall()
|
|
||||||
if res[0]['typname'] != 'numeric':
|
|
||||||
for line in (
|
|
||||||
"ALTER TABLE res_currency RENAME rounding TO rounding_bak",
|
|
||||||
"ALTER TABLE res_currency ADD rounding NUMERIC(12,6)",
|
|
||||||
"UPDATE res_currency SET rounding = power(10, - rounding_bak)",
|
|
||||||
"ALTER TABLE res_currency DROP rounding_bak",
|
|
||||||
):
|
|
||||||
cr.execute(line)
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
# ----------------------------- #
|
|
||||||
# drop constraint on ir_ui_view #
|
|
||||||
# ----------------------------- #
|
|
||||||
|
|
||||||
cr.execute('SELECT conname FROM pg_constraint where conname = \'ir_ui_view_type\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('ALTER TABLE ir_ui_view DROP CONSTRAINT ir_ui_view_type')
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
# ------------------------ #
|
|
||||||
# update res.partner.bank #
|
|
||||||
# ------------------------ #
|
|
||||||
|
|
||||||
cr.execute('SELECT a.attname FROM pg_class c, pg_attribute a WHERE c.relname = \'res_partner_bank\' AND a.attname = \'iban\' AND c.oid = a.attrelid')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('ALTER TABLE res_partner_bank RENAME iban TO acc_number')
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
# ------------------------------------------- #
|
|
||||||
# Add perm_id to ir_model and ir_model_fields #
|
|
||||||
# ------------------------------------------- #
|
|
||||||
|
|
||||||
cr.execute('SELECT a.attname FROM pg_class c, pg_attribute a WHERE c.relname = \'ir_model\' AND a.attname = \'perm_id\' AND c.oid = a.attrelid')
|
|
||||||
if not cr.fetchall():
|
|
||||||
cr.execute("ALTER TABLE ir_model ADD perm_id int references perm on delete set null")
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
cr.execute('SELECT a.attname FROM pg_class c, pg_attribute a WHERE c.relname = \'ir_model_fields\' AND a.attname = \'perm_id\' AND c.oid = a.attrelid')
|
|
||||||
if not cr.fetchall():
|
|
||||||
cr.execute("ALTER TABLE ir_model_fields ADD perm_id int references perm on delete set null")
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------- #
|
|
||||||
# remove name for all ir_act_window #
|
|
||||||
# --------------------------------- #
|
|
||||||
|
|
||||||
cr.execute("UPDATE ir_act_window SET name = ''")
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------ #
|
|
||||||
# Create a "allow none" default access to keep the behaviour of the system #
|
|
||||||
# ------------------------------------------------------------------------ #
|
|
||||||
|
|
||||||
cr.execute('SELECT model_id FROM ir_model_access')
|
|
||||||
res= cr.fetchall()
|
|
||||||
for r in res:
|
|
||||||
cr.execute('SELECT id FROM ir_model_access WHERE model_id = %d AND group_id IS NULL', (r[0],))
|
|
||||||
if not cr.fetchall():
|
|
||||||
cr.execute("INSERT into ir_model_access (name,model_id,group_id) VALUES ('Auto-generated access by migration',%d,NULL)",(r[0],))
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
# ------------------------------------------------- #
|
|
||||||
# Drop view report_account_analytic_line_to_invoice #
|
|
||||||
# ------------------------------------------------- #
|
|
||||||
|
|
||||||
cr.execute('SELECT viewname FROM pg_views WHERE viewname = \'report_account_analytic_line_to_invoice\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('DROP VIEW report_account_analytic_line_to_invoice')
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
# --------------------------- #
|
|
||||||
# Drop state from hr_employee #
|
|
||||||
# --------------------------- #
|
|
||||||
|
|
||||||
cr.execute('SELECT * FROM pg_class c, pg_attribute a WHERE c.relname=\'hr_employee\' AND a.attname=\'state\' AND c.oid=a.attrelid')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('ALTER TABLE hr_employee DROP state')
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
# ------------ #
|
|
||||||
# Add timezone #
|
|
||||||
# ------------ #
|
|
||||||
|
|
||||||
cr.execute('SELECT id FROM ir_values where model=\'res.users\' AND key=\'meta\' AND name=\'tz\'')
|
|
||||||
if not cr.fetchall():
|
|
||||||
import pytz, pickle
|
|
||||||
meta = pickle.dumps({'type':'selection', 'string':'Timezone', 'selection': [(x, x) for x in pytz.all_timezones]})
|
|
||||||
value = pickle.dumps(False)
|
|
||||||
cr.execute('INSERT INTO ir_values (name, key, model, meta, key2, object, value) VALUES (\'tz\', \'meta\', \'res.users\', %s, \'tz\', %s, %s)', (meta,False, value))
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
# ------------------------- #
|
|
||||||
# change product_uom factor #
|
|
||||||
# ------------------------- #
|
|
||||||
|
|
||||||
cr.execute('SELECT a.attname FROM pg_class c, pg_attribute a, pg_type t WHERE c.relname = \'product_uom\' AND a.attname = \'factor\' AND c.oid = a.attrelid AND a.atttypid = t.oid AND t.typname = \'float8\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('SELECT viewname FROM pg_views WHERE viewname = \'report_account_analytic_planning_stat_account\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('DROP VIEW report_account_analytic_planning_stat_account')
|
|
||||||
cr.execute('SELECT viewname FROM pg_views WHERE viewname = \'report_account_analytic_planning_stat\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('DROP VIEW report_account_analytic_planning_stat')
|
|
||||||
cr.execute('SELECT viewname FROM pg_views WHERE viewname = \'report_account_analytic_planning_stat_user\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('DROP VIEW report_account_analytic_planning_stat_user')
|
|
||||||
cr.execute('SELECT viewname FROM pg_views WHERE viewname = \'report_purchase_order_product\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('DROP VIEW report_purchase_order_product')
|
|
||||||
cr.execute('SELECT viewname FROM pg_views WHERE viewname = \'report_purchase_order_category\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('DROP VIEW report_purchase_order_category')
|
|
||||||
cr.execute('SELECT viewname FROM pg_views WHERE viewname = \'report_sale_order_product\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('DROP VIEW report_sale_order_product')
|
|
||||||
cr.execute('SELECT viewname FROM pg_views WHERE viewname = \'report_sale_order_category\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('DROP VIEW report_sale_order_category')
|
|
||||||
cr.execute('SELECT viewname FROM pg_views WHERE viewname = \'report_hr_timesheet_invoice_journal\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('DROP VIEW report_hr_timesheet_invoice_journal')
|
|
||||||
|
|
||||||
cr.execute('ALTER TABLE product_uom RENAME COLUMN factor to temp_column')
|
|
||||||
cr.execute('ALTER TABLE product_uom ADD COLUMN factor NUMERIC(12,6)')
|
|
||||||
cr.execute('UPDATE product_uom SET factor = temp_column')
|
|
||||||
cr.execute('ALTER TABLE product_uom ALTER factor SET NOT NULL')
|
|
||||||
cr.execute('ALTER TABLE product_uom DROP COLUMN temp_column')
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------- #
|
|
||||||
# Drop name_uniq constraint on stock_production_lot #
|
|
||||||
# ------------------------------------------------- #
|
|
||||||
|
|
||||||
cr.execute('SELECT conname FROM pg_constraint where conname = \'stock_production_lot_name_uniq\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('ALTER TABLE stock_production_lot DROP CONSTRAINT stock_production_lot_name_uniq')
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
# ------------------------------------ #
|
|
||||||
# Put country/state code in upper case #
|
|
||||||
# ------------------------------------ #
|
|
||||||
|
|
||||||
cr.execute('UPDATE res_country SET code = UPPER(code)')
|
|
||||||
cr.execute('UPDATE res_country_state SET code = UPPER(code)')
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
# --------------------------------------------- #
|
|
||||||
# Add primary key on tables inherits ir_actions #
|
|
||||||
# --------------------------------------------- #
|
|
||||||
|
|
||||||
cr.execute('SELECT indexname FROm pg_indexes WHERE indexname = \'ir_act_report_xml_pkey\' and tablename = \'ir_act_report_xml\'')
|
|
||||||
if not cr.fetchall():
|
|
||||||
cr.execute('ALTER TABLE ir_act_report_xml ADD PRIMARY KEY (id)')
|
|
||||||
cr.execute('SELECT indexname FROm pg_indexes WHERE indexname = \'ir_act_report_custom_pkey\' and tablename = \'ir_act_report_custom\'')
|
|
||||||
if not cr.fetchall():
|
|
||||||
cr.execute('ALTER TABLE ir_act_report_custom ADD PRIMARY KEY (id)')
|
|
||||||
cr.execute('SELECT indexname FROm pg_indexes WHERE indexname = \'ir_act_group_pkey\' and tablename = \'ir_act_group\'')
|
|
||||||
if not cr.fetchall():
|
|
||||||
cr.execute('ALTER TABLE ir_act_group ADD PRIMARY KEY (id)')
|
|
||||||
cr.execute('SELECT indexname FROm pg_indexes WHERE indexname = \'ir_act_execute_pkey\' and tablename = \'ir_act_execute\'')
|
|
||||||
if not cr.fetchall():
|
|
||||||
cr.execute('ALTER TABLE ir_act_execute ADD PRIMARY KEY (id)')
|
|
||||||
cr.execute('SELECT indexname FROm pg_indexes WHERE indexname = \'ir_act_wizard_pkey\' and tablename = \'ir_act_wizard\'')
|
|
||||||
if not cr.fetchall():
|
|
||||||
cr.execute('ALTER TABLE ir_act_wizard ADD PRIMARY KEY (id)')
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
cr.close
|
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Those scripts are provide as example of customization of migration scripts
|
|
|
@ -1,188 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# OpenERP, Open Source Management Solution
|
|
||||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as
|
|
||||||
# published by the Free Software Foundation, either version 3 of the
|
|
||||||
# License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
__author__ = 'Cédric Krier, <ced@tinyerp.com>'
|
|
||||||
__version__ = '0.1.0'
|
|
||||||
|
|
||||||
import psycopg
|
|
||||||
import optparse
|
|
||||||
import ConfigParser
|
|
||||||
|
|
||||||
# -----
|
|
||||||
|
|
||||||
parser = optparse.OptionParser(version="Tiny ERP server migration script " + __version__)
|
|
||||||
|
|
||||||
parser.add_option("-c", "--config", dest="config", help="specify path to Tiny ERP config file")
|
|
||||||
|
|
||||||
group = optparse.OptionGroup(parser, "Database related options")
|
|
||||||
group.add_option("--db_host", dest="db_host", help="specify the database host")
|
|
||||||
group.add_option("--db_port", dest="db_port", help="specify the database port")
|
|
||||||
group.add_option("-d", "--database", dest="db_name", help="specify the database name")
|
|
||||||
group.add_option("-r", "--db_user", dest="db_user", help="specify the database user name")
|
|
||||||
group.add_option("-w", "--db_password", dest="db_password", help="specify the database password")
|
|
||||||
parser.add_option_group(group)
|
|
||||||
|
|
||||||
options = optparse.Values()
|
|
||||||
options.db_name = 'terp' # default value
|
|
||||||
parser.parse_args(values=options)
|
|
||||||
|
|
||||||
if hasattr(options, 'config'):
|
|
||||||
configparser = ConfigParser.ConfigParser()
|
|
||||||
configparser.read([options.config])
|
|
||||||
for name, value in configparser.items('options'):
|
|
||||||
if not (hasattr(options, name) and getattr(options, name)):
|
|
||||||
if value in ('true', 'True'):
|
|
||||||
value = True
|
|
||||||
if value in ('false', 'False'):
|
|
||||||
value = False
|
|
||||||
setattr(options, name, value)
|
|
||||||
|
|
||||||
raise Exception('This script is provided as an example, you must custom it before')
|
|
||||||
|
|
||||||
# -----
|
|
||||||
|
|
||||||
host = hasattr(options, 'db_host') and "host=%s" % options.db_host or ''
|
|
||||||
port = hasattr(options, 'db_port') and "port=%s" % options.db_port or ''
|
|
||||||
name = "dbname=%s" % options.db_name
|
|
||||||
user = hasattr(options, 'db_user') and "user=%s" % options.db_user or ''
|
|
||||||
password = hasattr(options, 'db_password') and "password=%s" % options.db_password or ''
|
|
||||||
|
|
||||||
db = psycopg.connect('%s %s %s %s %s' % (host, port, name, user, password), serialize=0)
|
|
||||||
cr = db.cursor()
|
|
||||||
|
|
||||||
# fix country
|
|
||||||
|
|
||||||
|
|
||||||
cr.execute('SELECT code from res_country where code is not null group by code')
|
|
||||||
res = cr.fetchall()
|
|
||||||
|
|
||||||
for c in res:
|
|
||||||
cr.execute('SELECT max(id) from res_country where code = %s group by code', (c[0],))
|
|
||||||
res2 = cr.fetchone()
|
|
||||||
cr.execute('SELECT id from res_country where code = %s', (c[0],))
|
|
||||||
ids = ','.join(map(lambda x: str(x[0]), cr.fetchall()))
|
|
||||||
cr.execute('UPDATE res_partner_address set country_id = %d where country_id in ('+ids+')', (res2[0],))
|
|
||||||
cr.execute('DELETE FROM res_country WHERE code = %s and id <> %d', (c[0], res2[0],))
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
|
|
||||||
cr.execute('SELECT viewname FROM pg_views WHERE viewname = \'report_account_analytic_planning_stat\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('DROP VIEW report_account_analytic_planning_stat')
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
|
|
||||||
cr.execute('SELECT name from ( SELECT name, count(name) AS n FROM res_partner GROUP BY name ) AS foo WHERE n > 1')
|
|
||||||
res = cr.fetchall()
|
|
||||||
|
|
||||||
|
|
||||||
for p in res:
|
|
||||||
cr.execute('SELECT max(id) FROM res_partner WHERE name = %s GROUP BY name', (p[0],))
|
|
||||||
res2 = cr.fetchone()
|
|
||||||
cr.execute('UPDATE res_partner set active = False WHERE name = %s and id <> %d', (p[0], res2[0],))
|
|
||||||
cr.execute('SELECT id FROM res_partner WHERE name = %s AND id <> %d', (p[0], res2[0],))
|
|
||||||
res3 = cr.fetchall()
|
|
||||||
i = 0
|
|
||||||
for id in res3:
|
|
||||||
name = p[0]+' old'
|
|
||||||
if i:
|
|
||||||
name = name + ' ' + str(i)
|
|
||||||
cr.execute('UPDATE res_partner set name = %s WHERE id = %d', (name, id[0]))
|
|
||||||
i += 1
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
cr.execute('SELECT viewname FROM pg_views WHERE viewname = \'report_account_analytic_line_to_invoice\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('DROP VIEW report_account_analytic_line_to_invoice')
|
|
||||||
cr.execute('SELECT viewname FROM pg_views WHERE viewname = \'report_timesheet_invoice\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('drop VIEW report_timesheet_invoice')
|
|
||||||
cr.execute('SELECT viewname FROM pg_views WHERE viewname = \'report_purchase_order_category\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('drop VIEW report_purchase_order_category')
|
|
||||||
cr.execute('SELECT viewname FROM pg_views WHERE viewname = \'report_purchase_order_product\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('drop VIEW report_purchase_order_product')
|
|
||||||
cr.execute('SELECT viewname FROM pg_views WHERE viewname = \'report_sale_order_category\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('drop VIEW report_sale_order_category')
|
|
||||||
cr.execute('SELECT viewname FROM pg_views WHERE viewname = \'report_sale_order_product\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('drop VIEW report_sale_order_product')
|
|
||||||
cr.execute('SELECT viewname FROM pg_views WHERE viewname = \'report_timesheet_user\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('drop VIEW report_timesheet_user')
|
|
||||||
cr.execute('SELECT viewname FROM pg_views WHERE viewname = \'report_task_user_pipeline_open\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('drop VIEW report_task_user_pipeline_open')
|
|
||||||
cr.execute('SELECT viewname FROM pg_views WHERE viewname = \'hr_timesheet_sheet_sheet_day\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('drop VIEW hr_timesheet_sheet_sheet_day')
|
|
||||||
cr.execute('SELECT viewname FROM pg_views WHERE viewname = \'hr_timesheet_sheet_sheet_account\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('drop VIEW hr_timesheet_sheet_sheet_account')
|
|
||||||
cr.execute('SELECT viewname from pg_views where viewname = \'sale_journal_sale_stats\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('drop VIEW sale_journal_sale_stats')
|
|
||||||
cr.execute('SELECT viewname from pg_views where viewname = \'sale_journal_picking_stats\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('drop VIEW sale_journal_picking_stats')
|
|
||||||
cr.execute('SELECT viewname from pg_views where viewname = \'sale_journal_invoice_type_stats\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('drop VIEW sale_journal_invoice_type_stats')
|
|
||||||
|
|
||||||
cr.execute('ALTER TABLE product_template ALTER list_price TYPE numeric(16,2)')
|
|
||||||
cr.execute('ALTER TABLE product_template ALTER standard_price TYPE numeric(16,2)')
|
|
||||||
cr.execute('ALTER TABLE product_product ALTER price_extra TYPE numeric(16,2)')
|
|
||||||
cr.execute('ALTER TABLE product_product ALTER price_margin TYPE numeric(16,2)')
|
|
||||||
cr.execute('ALTER TABLE pricelist_partnerinfo ALTER price TYPE numeric(16,2)')
|
|
||||||
cr.execute('ALTER TABLE account_invoice_line ALTER price_unit TYPE numeric(16,2)')
|
|
||||||
cr.execute('ALTER TABLE purchase_order_line ALTER price_unit TYPE numeric(16,2)')
|
|
||||||
cr.execute('ALTER TABLE sale_order_line ALTER price_unit TYPE numeric(16,2)')
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
|
|
||||||
cr.execute('SELECT tablename FROM pg_tables WHERE tablename = \'subscription_document_fields\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('DROP TABLE subscription_document_fields')
|
|
||||||
cr.execute('SELECT tablename FROM pg_tables WHERE tablename = \'subscription_document\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('DROP TABLE subscription_document')
|
|
||||||
cr.execute('SELECT tablename FROM pg_tables WHERE tablename = \'subscription_subscription_history\'')
|
|
||||||
if cr.fetchall():
|
|
||||||
cr.execute('DROP TABLE subscription_subscription_history')
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
# -------------------- #
|
|
||||||
# Change currency rate #
|
|
||||||
# -------------------- #
|
|
||||||
|
|
||||||
cr.execute('SELECT a.attname FROM pg_class c, pg_attribute a WHERE c.relname = \'res_currency_rate\' AND a.attname = \'rate_old\' AND c.oid = a.attrelid')
|
|
||||||
if not cr.fetchall():
|
|
||||||
cr.execute('ALTER TABLE res_currency_rate ADD rate_old NUMERIC(12,6)')
|
|
||||||
cr.execute('UPDATE res_currency_rate SET rate_old = rate')
|
|
||||||
cr.execute('UPDATE res_currency_rate SET rate = (1 / rate_old)')
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
cr.close
|
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
||||||
|
|
|
@ -1,110 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# OpenERP, Open Source Management Solution
|
|
||||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as
|
|
||||||
# published by the Free Software Foundation, either version 3 of the
|
|
||||||
# License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
__version__ = '0.1.0'
|
|
||||||
|
|
||||||
import psycopg
|
|
||||||
import optparse
|
|
||||||
import ConfigParser
|
|
||||||
|
|
||||||
# -----
|
|
||||||
|
|
||||||
parser = optparse.OptionParser(version="Tiny ERP server migration script " + __version__)
|
|
||||||
|
|
||||||
parser.add_option("-c", "--config", dest="config", help="specify path to Tiny ERP config file")
|
|
||||||
|
|
||||||
group = optparse.OptionGroup(parser, "Database related options")
|
|
||||||
group.add_option("--db_host", dest="db_host", help="specify the database host")
|
|
||||||
group.add_option("--db_port", dest="db_port", help="specify the database port")
|
|
||||||
group.add_option("-d", "--database", dest="db_name", help="specify the database name")
|
|
||||||
group.add_option("-r", "--db_user", dest="db_user", help="specify the database user name")
|
|
||||||
group.add_option("-w", "--db_password", dest="db_password", help="specify the database password")
|
|
||||||
parser.add_option_group(group)
|
|
||||||
|
|
||||||
options = optparse.Values()
|
|
||||||
options.db_name = 'terp' # default value
|
|
||||||
parser.parse_args(values=options)
|
|
||||||
|
|
||||||
if hasattr(options, 'config'):
|
|
||||||
configparser = ConfigParser.ConfigParser()
|
|
||||||
configparser.read([options.config])
|
|
||||||
for name, value in configparser.items('options'):
|
|
||||||
if not (hasattr(options, name) and getattr(options, name)):
|
|
||||||
if value in ('true', 'True'):
|
|
||||||
value = True
|
|
||||||
if value in ('false', 'False'):
|
|
||||||
value = False
|
|
||||||
setattr(options, name, value)
|
|
||||||
|
|
||||||
# -----
|
|
||||||
|
|
||||||
host = hasattr(options, 'db_host') and "host=%s" % options.db_host or ''
|
|
||||||
port = hasattr(options, 'db_port') and "port=%s" % options.db_port or ''
|
|
||||||
name = "dbname=%s" % options.db_name
|
|
||||||
user = hasattr(options, 'db_user') and "user=%s" % options.db_user or ''
|
|
||||||
password = hasattr(options, 'db_password') and "password=%s" % options.db_password or ''
|
|
||||||
|
|
||||||
db = psycopg.connect('%s %s %s %s %s' % (host, port, name, user, password), serialize=0)
|
|
||||||
cr = db.cursor()
|
|
||||||
|
|
||||||
# ------------------------------ #
|
|
||||||
# drop not null on ir_attachment #
|
|
||||||
# ------------------------------ #
|
|
||||||
|
|
||||||
cr.execute('ALTER TABLE ir_attachment \
|
|
||||||
ALTER COLUMN res_model DROP NOT NULL, \
|
|
||||||
ALTER COLUMN res_id DROP NOT NULL')
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
# ---------------------------------- #
|
|
||||||
# change case date_deadline rounding #
|
|
||||||
# ---------------------------------- #
|
|
||||||
|
|
||||||
cr.execute("""SELECT
|
|
||||||
c.relname,a.attname,a.attlen,a.atttypmod,a.attnotnull,a.atthasdef,t.typname,CASE
|
|
||||||
WHEN a.attlen=-1 THEN a.atttypmod-4 ELSE a.attlen END as size FROM pg_class
|
|
||||||
c,pg_attribute a,pg_type t WHERE c.relname='crm_case' AND
|
|
||||||
a.attname='date_deadline' AND c.oid=a.attrelid AND a.atttypid=t.oid""")
|
|
||||||
|
|
||||||
res = cr.dictfetchall()
|
|
||||||
if res[0]['typname'] != 'timestamp':
|
|
||||||
for line in (
|
|
||||||
"ALTER TABLE crm_case RENAME date_deadline TO date_deadline_bak",
|
|
||||||
"ALTER TABLE crm_case ADD date_deadline timestamp",
|
|
||||||
"UPDATE crm_case SET date_deadline = date_deadline_bak",
|
|
||||||
"ALTER TABLE crm_case DROP date_deadline_bak",
|
|
||||||
):
|
|
||||||
cr.execute(line)
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
cr.execute('drop view report_task_user_pipeline_open');
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
cr.execute('alter table ir_model_fields add state varchar(26)')
|
|
||||||
cr.execute('alter table ir_model_fields add select_level varchar(3)')
|
|
||||||
cr.execute('alter table ir_act_wizard add primary key(id)')
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
|
|
||||||
cr.close()
|
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
||||||
|
|
|
@ -1,87 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# OpenERP, Open Source Management Solution
|
|
||||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as
|
|
||||||
# published by the Free Software Foundation, either version 3 of the
|
|
||||||
# License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
# TODO handle the case of zip modules
|
|
||||||
|
|
||||||
import os
|
|
||||||
import optparse
|
|
||||||
import sys
|
|
||||||
import glob
|
|
||||||
|
|
||||||
# TODO use the same function provided in openerp.modules
|
|
||||||
def load_information_from_description_file(module):
|
|
||||||
"""
|
|
||||||
:param module: The name of the module (sale, purchase, ...)
|
|
||||||
"""
|
|
||||||
for filename in ['__openerp__.py', '__terp__.py']:
|
|
||||||
description_file = os.path.join(module, filename)
|
|
||||||
if os.path.isfile(description_file):
|
|
||||||
return eval(file(description_file).read())
|
|
||||||
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def get_valid_path(paths, module):
|
|
||||||
for path in paths:
|
|
||||||
full = os.path.join(path, module)
|
|
||||||
if os.path.exists(full):
|
|
||||||
return full
|
|
||||||
return None
|
|
||||||
|
|
||||||
parser = optparse.OptionParser(usage="%prog [options] [module1 [module2 ...]]")
|
|
||||||
parser.add_option("-p", "--addons-path", dest="path", help="addons directory", action="append")
|
|
||||||
(opt, args) = parser.parse_args()
|
|
||||||
|
|
||||||
modules = []
|
|
||||||
if not opt.path:
|
|
||||||
opt.path = ["."]
|
|
||||||
|
|
||||||
if not args:
|
|
||||||
for path in opt.path:
|
|
||||||
modules += map(os.path.dirname, glob.glob(os.path.join(path, '*', '__openerp__.py')))
|
|
||||||
modules += map(os.path.dirname, glob.glob(os.path.join(path, '*', '__terp__.py')))
|
|
||||||
else:
|
|
||||||
for module in args:
|
|
||||||
valid_path = get_valid_path(opt.path, module)
|
|
||||||
if valid_path:
|
|
||||||
modules.append(valid_path)
|
|
||||||
|
|
||||||
all_modules = set(map(os.path.basename, modules))
|
|
||||||
print 'digraph G {'
|
|
||||||
while len(modules):
|
|
||||||
f = modules.pop(0)
|
|
||||||
module_name = os.path.basename(f)
|
|
||||||
all_modules.add(module_name)
|
|
||||||
info = load_information_from_description_file(f)
|
|
||||||
if info.get('installable', True):
|
|
||||||
for name in info.get('depends',[]):
|
|
||||||
valid_path = get_valid_path(opt.path, name)
|
|
||||||
if name not in all_modules:
|
|
||||||
if valid_path:
|
|
||||||
modules.append(valid_path)
|
|
||||||
else:
|
|
||||||
all_modules.add(name)
|
|
||||||
print '\t%s [color=red]' % (name,)
|
|
||||||
print '\t%s -> %s;' % (module_name, name)
|
|
||||||
print '}'
|
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
||||||
|
|
|
@ -1,347 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# OpenERP, Open Source Management Solution
|
|
||||||
# Copyright (C) 2004-2010 OpenERP SA (<http://openerp.com>).
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as
|
|
||||||
# published by the Free Software Foundation, either version 3 of the
|
|
||||||
# License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
"""
|
|
||||||
Experimental script for conversion between OpenERP's XML serialization format
|
|
||||||
and the new YAML serialization format introduced in OpenERP 6.0.
|
|
||||||
Intended to be used as a quick preprocessor for converting data/test files, then
|
|
||||||
to be fine-tuned manually.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import yaml
|
|
||||||
import logging
|
|
||||||
from lxml import etree
|
|
||||||
|
|
||||||
__VERSION__ = '0.0.2'
|
|
||||||
|
|
||||||
def toString(value):
|
|
||||||
value='"' + value + '"'
|
|
||||||
return value
|
|
||||||
|
|
||||||
class XmlTag(etree.ElementBase):
|
|
||||||
def _to_yaml(self):
|
|
||||||
child_tags = []
|
|
||||||
for node in self:
|
|
||||||
if hasattr(node, '_to_yaml'):
|
|
||||||
child_tags.append(node._to_yaml())
|
|
||||||
return self.tag(attrib=self.attrib, child_tags=child_tags)
|
|
||||||
|
|
||||||
class YamlTag(object):
|
|
||||||
"""
|
|
||||||
Superclass for constructors of custom tags defined in yaml file.
|
|
||||||
"""
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
self.__dict__.update(kwargs)
|
|
||||||
self.attrib = self.__dict__.get('attrib', {})
|
|
||||||
self.child_tags = self.__dict__.get('child_tags', '')
|
|
||||||
def __getitem__(self, key):
|
|
||||||
return getattr(self, key)
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
return None
|
|
||||||
def __repr__(self):
|
|
||||||
for k,v in self.attrib.iteritems():
|
|
||||||
if str(v) and str(v)[0] in ['[', '{', '#', '*', '(']:
|
|
||||||
self.attrib[k] = toString(self.attrib[k]).replace("'", '')
|
|
||||||
st = self.yaml_tag + ' ' + str(self.attrib)
|
|
||||||
return st
|
|
||||||
|
|
||||||
# attrib tags
|
|
||||||
class ref(YamlTag):
|
|
||||||
yaml_tag = u'!ref'
|
|
||||||
def __init__(self, expr="False"):
|
|
||||||
self.expr = expr
|
|
||||||
def __repr__(self):
|
|
||||||
return "'%s'"%str(self.expr)
|
|
||||||
|
|
||||||
class Eval(YamlTag):
|
|
||||||
yaml_tag = u'!eval'
|
|
||||||
def __init__(self, expr="False"):
|
|
||||||
self.expr = expr
|
|
||||||
def __repr__(self):
|
|
||||||
value=str(self.expr)
|
|
||||||
if value.find("6,") != -1:
|
|
||||||
value = eval(str(eval(value)))
|
|
||||||
value=value[0][2]
|
|
||||||
value = [[value]]
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
value=int(value)
|
|
||||||
except:
|
|
||||||
if value and value[0] in ['[', '{', '#', '*', '(']:
|
|
||||||
value = value.replace('"', r'\"')
|
|
||||||
value = toString(value)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
value = eval(value)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return value
|
|
||||||
|
|
||||||
class Search(YamlTag):
|
|
||||||
yaml_tag = u'!ref'
|
|
||||||
|
|
||||||
# test tag
|
|
||||||
class xml_test(XmlTag):
|
|
||||||
def _to_yaml(self):
|
|
||||||
expr = self.attrib.get('expr')
|
|
||||||
text = self.text
|
|
||||||
if text:
|
|
||||||
expr = expr + ' == ' + '"%s"'%text
|
|
||||||
return [[expr]]
|
|
||||||
|
|
||||||
class xml_data(etree.ElementBase):
|
|
||||||
def _to_yaml(self):
|
|
||||||
value = self.attrib.get('noupdate', "0")
|
|
||||||
return data(value)
|
|
||||||
|
|
||||||
# field tag:
|
|
||||||
class xml_field(etree.ElementBase):
|
|
||||||
def _to_yaml(self):
|
|
||||||
field = ' ' + self.attrib.pop('name','unknown')
|
|
||||||
|
|
||||||
if self.attrib.get('search', None):
|
|
||||||
value = Search(attrib=self.attrib, child_tags='').__repr__()
|
|
||||||
else:
|
|
||||||
attr = (self.attrib.get('ref', None) and 'ref') or (self.attrib.get('eval', None) and 'eval') or 'None'
|
|
||||||
value = Eval(self.attrib.get(attr, self.text)).__repr__() or ''
|
|
||||||
return {field: value}
|
|
||||||
|
|
||||||
# value tag
|
|
||||||
class xml_value(etree.ElementBase):
|
|
||||||
def _to_yaml(self):
|
|
||||||
|
|
||||||
if self.attrib.get('eval', None):
|
|
||||||
key, val = 'eval', '"'+self.attrib.get('eval')+'"'
|
|
||||||
elif self.attrib.get('model', None):
|
|
||||||
key, val = 'model', self.attrib.get('model')
|
|
||||||
val=val.replace("'",'""')
|
|
||||||
self.attrib.pop(key)
|
|
||||||
d={}
|
|
||||||
for k,v in self.attrib.iteritems():
|
|
||||||
if k == 'search':
|
|
||||||
v = '"' + v + '"'
|
|
||||||
k='--' + k
|
|
||||||
v=v.replace("'",'""')
|
|
||||||
d[k] = v
|
|
||||||
if d:
|
|
||||||
ls=[[{key:val},dict(d)]]
|
|
||||||
else:
|
|
||||||
ls=[[{key:val}]]
|
|
||||||
return ls
|
|
||||||
|
|
||||||
# data tag
|
|
||||||
class data(YamlTag):
|
|
||||||
yaml_tag = u'!context'
|
|
||||||
def __init__(self, noupdate="0"):
|
|
||||||
self.child_tags = {' noupdate':noupdate}
|
|
||||||
def __repr__(self):
|
|
||||||
return "!!context"
|
|
||||||
|
|
||||||
# Record tag
|
|
||||||
class Record(YamlTag):
|
|
||||||
yaml_tag = u'!record'
|
|
||||||
class xml_record(XmlTag):
|
|
||||||
tag=Record
|
|
||||||
def _to_yaml(self):
|
|
||||||
child_tags = {}
|
|
||||||
for node in self:
|
|
||||||
if hasattr(node, '_to_yaml'):
|
|
||||||
child_tags.update(node._to_yaml())
|
|
||||||
return Record(attrib=self.attrib, child_tags=child_tags)
|
|
||||||
|
|
||||||
# ir_set tag
|
|
||||||
class Ir_Set(YamlTag):
|
|
||||||
yaml_tag = u'!ir_set'
|
|
||||||
def __repr__(self):
|
|
||||||
st = self.yaml_tag
|
|
||||||
return st
|
|
||||||
class xml_ir_set(XmlTag):
|
|
||||||
tag=Ir_Set
|
|
||||||
def _to_yaml(self):
|
|
||||||
child_tags = {}
|
|
||||||
for node in self:
|
|
||||||
if hasattr(node, '_to_yaml'):
|
|
||||||
child_tags.update(node._to_yaml())
|
|
||||||
return Ir_Set(attrib=self.attrib, child_tags=child_tags)
|
|
||||||
|
|
||||||
# workflow tag
|
|
||||||
class Workflow(YamlTag):
|
|
||||||
yaml_tag = u'!workflow'
|
|
||||||
class xml_workflow(XmlTag):
|
|
||||||
tag=Workflow
|
|
||||||
|
|
||||||
# function tag
|
|
||||||
class Function(YamlTag):
|
|
||||||
yaml_tag = u'!function'
|
|
||||||
class xml_function(XmlTag):
|
|
||||||
tag=Function
|
|
||||||
|
|
||||||
# function tag
|
|
||||||
class Assert(YamlTag):
|
|
||||||
yaml_tag = u'!assert'
|
|
||||||
class xml_assert(XmlTag):
|
|
||||||
tag=Assert
|
|
||||||
|
|
||||||
# menuitem tagresult.append(yaml.safe_dump(obj, default_flow_style=False, allow_unicode=True).replace("'",''))
|
|
||||||
class MenuItem(YamlTag):
|
|
||||||
yaml_tag = u'!menuitem'
|
|
||||||
class xml_menuitem(XmlTag):
|
|
||||||
tag=MenuItem
|
|
||||||
|
|
||||||
# act_window tag
|
|
||||||
class ActWindow(YamlTag):
|
|
||||||
yaml_tag = u'!act_window'
|
|
||||||
class xml_act_window(XmlTag):
|
|
||||||
tag=ActWindow
|
|
||||||
|
|
||||||
# report tag
|
|
||||||
class Report(YamlTag):
|
|
||||||
yaml_tag = u'!report'
|
|
||||||
class xml_report(XmlTag):
|
|
||||||
tag=Report
|
|
||||||
|
|
||||||
# deletes tag
|
|
||||||
class Delete(YamlTag):
|
|
||||||
yaml_tag = u'!delete'
|
|
||||||
class xml_delete(XmlTag):
|
|
||||||
tag=Delete
|
|
||||||
|
|
||||||
# python tag
|
|
||||||
class Python(YamlTag):
|
|
||||||
yaml_tag = u'!python'
|
|
||||||
class xml_python(XmlTag):
|
|
||||||
tag=Python
|
|
||||||
|
|
||||||
# context tag
|
|
||||||
class Context(YamlTag):
|
|
||||||
yaml_tag = u'!context'
|
|
||||||
class xml_context(XmlTag):
|
|
||||||
tag=Context
|
|
||||||
|
|
||||||
# url tag
|
|
||||||
class Url(YamlTag):
|
|
||||||
yaml_tag = u'!url'
|
|
||||||
class xml_url(XmlTag):
|
|
||||||
tag=Url
|
|
||||||
|
|
||||||
# delete tag
|
|
||||||
class Delete(YamlTag):
|
|
||||||
yaml_tag = u'!delete'
|
|
||||||
class xml_delete(XmlTag):
|
|
||||||
tag=Delete
|
|
||||||
|
|
||||||
def represent_data(dumper, data):
|
|
||||||
return dumper.represent_mapping(u'tag:yaml.org,2002:map', [('!'+str(data), data.child_tags)])
|
|
||||||
|
|
||||||
yaml.SafeDumper.add_representer(Record, represent_data)
|
|
||||||
yaml.SafeDumper.add_representer(data, represent_data)
|
|
||||||
yaml.SafeDumper.add_representer(Workflow, represent_data)
|
|
||||||
yaml.SafeDumper.add_representer(Function, represent_data)
|
|
||||||
yaml.SafeDumper.add_representer(Assert, represent_data)
|
|
||||||
yaml.SafeDumper.add_representer(MenuItem, represent_data)
|
|
||||||
yaml.SafeDumper.add_representer(Ir_Set, represent_data)
|
|
||||||
yaml.SafeDumper.add_representer(Python, represent_data)
|
|
||||||
yaml.SafeDumper.add_representer(Context, represent_data)
|
|
||||||
|
|
||||||
class MyLookup(etree.CustomElementClassLookup):
|
|
||||||
def lookup(self, node_type, document, namespace, name):
|
|
||||||
if node_type=='element':
|
|
||||||
return {
|
|
||||||
'data': xml_data,
|
|
||||||
'record': xml_record,
|
|
||||||
'field': xml_field,
|
|
||||||
'workflow': xml_workflow,
|
|
||||||
'function': xml_function,
|
|
||||||
'value': xml_value,
|
|
||||||
'assert': xml_assert,
|
|
||||||
'test': xml_test,
|
|
||||||
'menuitem': xml_menuitem,
|
|
||||||
'act_window': xml_act_window,
|
|
||||||
'report': xml_report,
|
|
||||||
'delete': xml_delete,
|
|
||||||
'python': xml_python,
|
|
||||||
'context': xml_context,
|
|
||||||
'url': xml_url,
|
|
||||||
'ir_set': xml_ir_set,
|
|
||||||
}.get(name, None)
|
|
||||||
elif node_type=='comment':
|
|
||||||
return None#xml_comment
|
|
||||||
return None
|
|
||||||
|
|
||||||
class xml_parse(object):
|
|
||||||
def __init__(self):
|
|
||||||
self.context = {}
|
|
||||||
def parse(self, fname):
|
|
||||||
parser = etree.XMLParser()
|
|
||||||
parser.setElementClassLookup(MyLookup())
|
|
||||||
result = []
|
|
||||||
self.root = etree.XML(file(fname).read(), parser)
|
|
||||||
for data in self.root:
|
|
||||||
if hasattr(data, '_to_yaml'):
|
|
||||||
obj = data._to_yaml()
|
|
||||||
if obj.yaml_tag == '!context':
|
|
||||||
result.append(yaml.dump(str(obj)).replace("'",'').split('\n')[0])
|
|
||||||
result.append(yaml.dump(obj.child_tags, default_flow_style=False).replace("'",''))
|
|
||||||
else:
|
|
||||||
result.append(yaml.safe_dump(obj, default_flow_style=False, allow_unicode=True).replace("'",''))
|
|
||||||
self.context.update(data.attrib)
|
|
||||||
for tag in data:
|
|
||||||
if tag.tag == etree.Comment:
|
|
||||||
result.append(tag)
|
|
||||||
else:
|
|
||||||
if hasattr(tag, '_to_yaml'):
|
|
||||||
obj = tag._to_yaml()
|
|
||||||
if not obj.child_tags:
|
|
||||||
result.append(yaml.dump('!'+str(obj), default_flow_style=False, allow_unicode=True, width=999).replace("'",''))
|
|
||||||
else:
|
|
||||||
result.append(yaml.safe_dump(obj, default_flow_style=False, allow_unicode=True, width=999).replace('\n:', ':\n').replace("'",''))
|
|
||||||
print "# Experimental OpenERP xml-to-yml conversion! (v%s)"%__VERSION__
|
|
||||||
print "# Please use this as a first conversion/preprocessing step,"
|
|
||||||
print "# not as a production-ready tool!"
|
|
||||||
for record in result:
|
|
||||||
if type(record) != type(''):
|
|
||||||
record=str(record)
|
|
||||||
l= record.split("\n")
|
|
||||||
for line in l:
|
|
||||||
print '#' + str(line)
|
|
||||||
continue
|
|
||||||
record=str(record)
|
|
||||||
record = record.replace('- --',' ') #for value tag
|
|
||||||
record = record.replace('!!', '- \n !') #for all parent tags
|
|
||||||
record = record.replace('- - -', ' -') #for many2many fields
|
|
||||||
record = record.replace('? ', '') #for long expressions
|
|
||||||
record = record.replace('""', "'") #for string-value under value tag
|
|
||||||
print record
|
|
||||||
|
|
||||||
if __name__=='__main__':
|
|
||||||
import optparse
|
|
||||||
import sys
|
|
||||||
parser = optparse.OptionParser(
|
|
||||||
usage = '%s file.xml' % sys.argv[0])
|
|
||||||
(opt, args) = parser.parse_args()
|
|
||||||
if len(args) != 1:
|
|
||||||
parser.error("incorrect number of arguments")
|
|
||||||
fname = sys.argv[1]
|
|
||||||
p = xml_parse()
|
|
||||||
p.parse(fname)
|
|
||||||
|
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
|
@ -1,89 +0,0 @@
|
||||||
# X.509 Certificate options
|
|
||||||
#
|
|
||||||
# DN options
|
|
||||||
|
|
||||||
# The organization of the subject.
|
|
||||||
organization = "Some organization."
|
|
||||||
|
|
||||||
# The organizational unit of the subject.
|
|
||||||
unit = "ERP dept."
|
|
||||||
|
|
||||||
# The locality of the subject.
|
|
||||||
# locality =
|
|
||||||
|
|
||||||
# The state of the certificate owner.
|
|
||||||
state = "State"
|
|
||||||
|
|
||||||
# The country of the subject. Two letter code.
|
|
||||||
country = BE
|
|
||||||
|
|
||||||
# The common name of the certificate owner.
|
|
||||||
cn = "Some company"
|
|
||||||
|
|
||||||
# A user id of the certificate owner.
|
|
||||||
#uid = "clauper"
|
|
||||||
|
|
||||||
# If the supported DN OIDs are not adequate you can set
|
|
||||||
# any OID here.
|
|
||||||
# For example set the X.520 Title and the X.520 Pseudonym
|
|
||||||
# by using OID and string pairs.
|
|
||||||
#dn_oid = "2.5.4.12" "Dr." "2.5.4.65" "jackal"
|
|
||||||
|
|
||||||
# This is deprecated and should not be used in new
|
|
||||||
# certificates.
|
|
||||||
# pkcs9_email = "none@none.org"
|
|
||||||
|
|
||||||
# The serial number of the certificate
|
|
||||||
serial = 001
|
|
||||||
|
|
||||||
# In how many days, counting from today, this certificate will expire.
|
|
||||||
expiration_days = 700
|
|
||||||
|
|
||||||
# X.509 v3 extensions
|
|
||||||
|
|
||||||
# A dnsname in case of a WWW server.
|
|
||||||
#dns_name = "www.none.org"
|
|
||||||
#dns_name = "www.morethanone.org"
|
|
||||||
|
|
||||||
# An IP address in case of a server.
|
|
||||||
#ip_address = "192.168.1.1"
|
|
||||||
|
|
||||||
# An email in case of a person
|
|
||||||
email = "none@none.org"
|
|
||||||
|
|
||||||
# An URL that has CRLs (certificate revocation lists)
|
|
||||||
# available. Needed in CA certificates.
|
|
||||||
#crl_dist_points = "http://www.getcrl.crl/getcrl/"
|
|
||||||
|
|
||||||
# Whether this is a CA certificate or not
|
|
||||||
#ca
|
|
||||||
|
|
||||||
# Whether this certificate will be used for a TLS client
|
|
||||||
#tls_www_client
|
|
||||||
|
|
||||||
# Whether this certificate will be used for a TLS server
|
|
||||||
tls_www_server
|
|
||||||
|
|
||||||
# Whether this certificate will be used to sign data (needed
|
|
||||||
# in TLS DHE ciphersuites).
|
|
||||||
#signing_key
|
|
||||||
|
|
||||||
# Whether this certificate will be used to encrypt data (needed
|
|
||||||
# in TLS RSA ciphersuites). Note that it is prefered to use different
|
|
||||||
# keys for encryption and signing.
|
|
||||||
encryption_key
|
|
||||||
|
|
||||||
# Whether this key will be used to sign other certificates.
|
|
||||||
#cert_signing_key
|
|
||||||
|
|
||||||
# Whether this key will be used to sign CRLs.
|
|
||||||
#crl_signing_key
|
|
||||||
|
|
||||||
# Whether this key will be used to sign code.
|
|
||||||
#code_signing_key
|
|
||||||
|
|
||||||
# Whether this key will be used to sign OCSP data.
|
|
||||||
#ocsp_signing_key
|
|
||||||
|
|
||||||
# Whether this key will be used for time stamping.
|
|
||||||
#time_stamping_key
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
#!/usr/bin/env python2
|
||||||
|
#----------------------------------------------------------
|
||||||
|
# odoo cli
|
||||||
|
#
|
||||||
|
# To install your odoo development environement type:
|
||||||
|
#
|
||||||
|
# wget -O- https://raw.githubusercontent.com/odoo/odoo/master/odoo.py | python
|
||||||
|
#
|
||||||
|
# The setup_* subcommands used to boostrap odoo are defined here inline and may
|
||||||
|
# only depends on the python 2.7 stdlib
|
||||||
|
#
|
||||||
|
# The rest of subcommands are defined in odoo/cli or in <module>/cli by
|
||||||
|
# subclassing the Command object
|
||||||
|
#
|
||||||
|
# https://raw.githubusercontent.com/odoo-dev/odoo/master-odoo-cmd-fme/odoo.py
|
||||||
|
#
|
||||||
|
#----------------------------------------------------------
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
GIT_HOOKS_PRE_PUSH = """
|
||||||
|
#!/usr/bin/env python2
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
if re.search('github.com[:/]odoo/odoo.git$', sys.argv[2]):
|
||||||
|
print "Pushing to /odoo/odoo.git is forbidden, please push to odoo-dev, use -f to override"
|
||||||
|
sys.exit(1)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def printf(f,*l):
|
||||||
|
print "odoo:" + f % l
|
||||||
|
|
||||||
|
def run(*l):
|
||||||
|
if isinstance(l[0], list):
|
||||||
|
l = l[0]
|
||||||
|
printf("running %s", " ".join(l))
|
||||||
|
subprocess.check_call(l)
|
||||||
|
|
||||||
|
def git_locate():
|
||||||
|
# Locate git dir
|
||||||
|
# TODO add support for os.environ.get('GIT_DIR')
|
||||||
|
|
||||||
|
# check for an odoo child
|
||||||
|
if os.path.isfile('odoo/.git/config'):
|
||||||
|
os.chdir('odoo')
|
||||||
|
|
||||||
|
path = os.getcwd()
|
||||||
|
while path != '/':
|
||||||
|
gitconfig_path = os.path.join(path, '.git/config')
|
||||||
|
if os.path.isfile(gitconfig_path):
|
||||||
|
content = open(gitconfig_path).read()
|
||||||
|
if re.search('github.com[:/]odoo/odoo.git', content):
|
||||||
|
break
|
||||||
|
path = os.path.dirname(path)
|
||||||
|
if path == '/':
|
||||||
|
path = None
|
||||||
|
return path
|
||||||
|
|
||||||
|
def cmd_setup_git_init():
|
||||||
|
git_dir = git_locate()
|
||||||
|
if git_dir:
|
||||||
|
printf('git repo found at %s',git_dir)
|
||||||
|
else:
|
||||||
|
run("git", "init", "odoo")
|
||||||
|
git_dir = os.path.join(os.getcwd(), 'odoo')
|
||||||
|
if git_dir:
|
||||||
|
# sane push config for git < 2.0
|
||||||
|
run('git','config','push.default','simple')
|
||||||
|
# merge bzr style
|
||||||
|
run('git','config','merge.ff','no')
|
||||||
|
run('git','config','merge.commit','no')
|
||||||
|
# push hooks
|
||||||
|
pre_push_path = os.path.join(git_dir, '.git/hooks/pre-push')
|
||||||
|
open(pre_push_path,'w').write(GIT_HOOKS_PRE_PUSH.strip())
|
||||||
|
os.chmod(pre_push_path, 0755)
|
||||||
|
# setup odoo remote
|
||||||
|
run('git','config','remote.odoo.url','https://github.com/odoo/odoo.git')
|
||||||
|
run('git','config','remote.odoo.pushurl','git@github.com:odoo/odoo.git')
|
||||||
|
run('git','config','--add','remote.odoo.fetch','dummy')
|
||||||
|
run('git','config','--unset-all','remote.odoo.fetch')
|
||||||
|
run('git','config','--add','remote.odoo.fetch','+refs/heads/*:refs/remotes/odoo/heads/*')
|
||||||
|
# setup odoo-dev remote
|
||||||
|
run('git','config','remote.odoo-dev.url','https://github.com/odoo-dev/odoo.git')
|
||||||
|
run('git','config','remote.odoo-dev.pushurl','git@github.com:odoo-dev/odoo.git')
|
||||||
|
run('git','remote','update')
|
||||||
|
# setup master branch
|
||||||
|
run('git','config','branch.master.remote','odoo')
|
||||||
|
run('git','config','branch.master.merge','refs/heads/master')
|
||||||
|
run('git','checkout','master')
|
||||||
|
else:
|
||||||
|
printf('no git repo found')
|
||||||
|
|
||||||
|
def cmd_setup_git_odoo_dev():
|
||||||
|
git_dir = git_locate()
|
||||||
|
if git_dir:
|
||||||
|
# setup odoo-dev remote
|
||||||
|
run('git','config','--add','remote.odoo-dev.fetch','dummy')
|
||||||
|
run('git','config','--unset-all','remote.odoo-dev.fetch')
|
||||||
|
run('git','config','--add','remote.odoo-dev.fetch','+refs/heads/*:refs/remotes/odoo-dev/heads/*')
|
||||||
|
run('git','config','--add','remote.odoo-dev.fetch','+refs/pull/*:refs/remotes/odoo-dev/pull/*')
|
||||||
|
run('git','remote','update')
|
||||||
|
|
||||||
|
def cmd_setup_git_odoo_review():
|
||||||
|
git_dir = git_locate()
|
||||||
|
if git_dir:
|
||||||
|
# setup odoo-dev remote
|
||||||
|
run('git','config','--add','remote.odoo.fetch','dummy')
|
||||||
|
run('git','config','--unset-all','remote.odoo.fetch')
|
||||||
|
run('git','config','--add','remote.odoo.fetch','+refs/heads/*:refs/remotes/odoo/heads/*')
|
||||||
|
run('git','config','--add','remote.odoo.fetch','+refs/tags/*:refs/remotes/odoo/tags/*')
|
||||||
|
run('git','config','--add','remote.odoo.fetch','+refs/pull/*:refs/remotes/odoo/pull/*')
|
||||||
|
|
||||||
|
def setup_deps_debian(git_dir):
|
||||||
|
debian_control_path = os.path.join(git_dir, 'debian/control')
|
||||||
|
debian_control = open(debian_control_path).read()
|
||||||
|
debs = re.findall('python-[0-9a-z]+',debian_control)
|
||||||
|
proc = subprocess.Popen(['sudo','apt-get','install'] + debs, stdin=open('/dev/tty'))
|
||||||
|
proc.communicate()
|
||||||
|
|
||||||
|
def cmd_setup_deps():
|
||||||
|
git_dir = git_locate()
|
||||||
|
if git_dir:
|
||||||
|
if os.path.isfile('/etc/debian_version'):
|
||||||
|
setup_deps_debian(git_dir)
|
||||||
|
|
||||||
|
def setup_pg_debian(git_dir):
|
||||||
|
cmd = ['sudo','su','-','postgres','-c','createuser -s %s' % os.environ['USER']]
|
||||||
|
subprocess.call(cmd)
|
||||||
|
|
||||||
|
def cmd_setup_pg():
|
||||||
|
git_dir = git_locate()
|
||||||
|
if git_dir:
|
||||||
|
if os.path.isfile('/etc/debian_version'):
|
||||||
|
setup_pg_debian(git_dir)
|
||||||
|
|
||||||
|
def cmd_setup():
|
||||||
|
cmd_setup_git_init()
|
||||||
|
cmd_setup_deps()
|
||||||
|
cmd_setup_pg()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# regsitry of commands
|
||||||
|
g = globals()
|
||||||
|
cmds = dict([(i[4:],g[i]) for i in g if i.startswith('cmd_')])
|
||||||
|
# if curl URL | python2 then use command setup
|
||||||
|
if len(sys.argv) == 1 and __file__ == '<stdin>':
|
||||||
|
cmd_setup()
|
||||||
|
elif len(sys.argv) == 2 and sys.argv[1] in cmds:
|
||||||
|
cmds[sys.argv[1]]()
|
||||||
|
else:
|
||||||
|
import openerp
|
||||||
|
openerp.cli.main()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
8
oe
|
@ -1,8 +0,0 @@
|
||||||
#! /usr/bin/env python2
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
import sys
|
|
||||||
if len(sys.argv) > 1 and sys.argv[1] == 'run-tests':
|
|
||||||
sys.exit(0)
|
|
||||||
import openerpcommand.main
|
|
||||||
openerpcommand.main.run()
|
|
|
@ -1,63 +0,0 @@
|
||||||
import argparse
|
|
||||||
import textwrap
|
|
||||||
|
|
||||||
from .call import Call
|
|
||||||
from .client import Open, Show, ConsumeNothing, ConsumeMemory, LeakMemory, ConsumeCPU
|
|
||||||
from .benchmarks import Bench, BenchRead, BenchFieldsViewGet, BenchDummy, BenchLogin
|
|
||||||
from .bench_sale_mrp import BenchSaleMrp
|
|
||||||
from . import common
|
|
||||||
|
|
||||||
from . import conf # Not really server-side (in the `for` below).
|
|
||||||
from . import cron
|
|
||||||
from . import drop
|
|
||||||
from . import initialize
|
|
||||||
from . import model
|
|
||||||
from . import module
|
|
||||||
from . import read
|
|
||||||
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,
|
|
||||||
scaffold, uninstall, update, web, grunt_tests, )
|
|
||||||
|
|
||||||
command_list_client = (Call, Open, Show, ConsumeNothing, ConsumeMemory,
|
|
||||||
LeakMemory, ConsumeCPU, Bench, BenchRead,
|
|
||||||
BenchFieldsViewGet, BenchDummy, BenchLogin,
|
|
||||||
BenchSaleMrp, )
|
|
||||||
|
|
||||||
def main_parser():
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
usage=argparse.SUPPRESS,
|
|
||||||
description=textwrap.fill(textwrap.dedent("""\
|
|
||||||
OpenERP Command provides a set of command-line tools around
|
|
||||||
the OpenERP framework: openobject-server. All the tools are
|
|
||||||
sub-commands of a single oe executable.""")),
|
|
||||||
epilog="""Use <command> --help to get information about the command.""",
|
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
||||||
)
|
|
||||||
description = []
|
|
||||||
for x in command_list_server:
|
|
||||||
description.append(x.__name__[len(__package__)+1:])
|
|
||||||
if x.__doc__:
|
|
||||||
description.extend([
|
|
||||||
":\n",
|
|
||||||
textwrap.fill(str(x.__doc__).strip(),
|
|
||||||
subsequent_indent=' ',
|
|
||||||
initial_indent=' '),
|
|
||||||
])
|
|
||||||
description.append("\n\n")
|
|
||||||
subparsers = parser.add_subparsers(
|
|
||||||
title="Available commands",
|
|
||||||
help=argparse.SUPPRESS,
|
|
||||||
description="".join(description[:-1]),
|
|
||||||
)
|
|
||||||
# Server-side commands.
|
|
||||||
for x in command_list_server:
|
|
||||||
x.add_parser(subparsers)
|
|
||||||
# Client-side commands. TODO one per .py file.
|
|
||||||
for x in command_list_client:
|
|
||||||
x(subparsers)
|
|
||||||
return parser
|
|
|
@ -1,3 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Nothing here, the module provides only data.
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
|
@ -1,15 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
{
|
|
||||||
'name': 'bench_sale_mrp',
|
|
||||||
'version': '0.1',
|
|
||||||
'category': 'Benchmarks',
|
|
||||||
'description': """Prepare some data to run a benchmark.""",
|
|
||||||
'author': 'OpenERP SA',
|
|
||||||
'maintainer': 'OpenERP SA',
|
|
||||||
'website': 'http://www.openerp.com',
|
|
||||||
'depends': ['base', 'sale_mrp'],
|
|
||||||
'data': ['data.yml'],
|
|
||||||
'installable': True,
|
|
||||||
'auto_install': False,
|
|
||||||
}
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
|
@ -1,41 +0,0 @@
|
||||||
-
|
|
||||||
This is a subset of `sale_mrp/test/sale_mrp.yml`.
|
|
||||||
-
|
|
||||||
I define a product category `Mobile Products Sellable`.
|
|
||||||
-
|
|
||||||
!record {model: product.category, id: my_product_category_0}:
|
|
||||||
name: Mobile Products Sellable
|
|
||||||
-
|
|
||||||
I define a product `Slider Mobile`
|
|
||||||
-
|
|
||||||
!record {model: product.product, id: my_slider_mobile_0}:
|
|
||||||
categ_id: my_product_category_0
|
|
||||||
cost_method: standard
|
|
||||||
list_price: 200.0
|
|
||||||
mes_type: fixed
|
|
||||||
name: Slider Mobile
|
|
||||||
procure_method: make_to_order
|
|
||||||
seller_delay: '1'
|
|
||||||
seller_ids:
|
|
||||||
- delay: 1
|
|
||||||
name: base.res_partner_agrolait
|
|
||||||
min_qty: 2.0
|
|
||||||
qty: 5.0
|
|
||||||
standard_price: 189.0
|
|
||||||
supply_method: produce
|
|
||||||
type: product
|
|
||||||
uom_id: product.product_uom_unit
|
|
||||||
uom_po_id: product.product_uom_unit
|
|
||||||
-
|
|
||||||
I create a Bill of Material for the `Slider Mobile` product.
|
|
||||||
-
|
|
||||||
!record {model: mrp.bom, id: mrp_bom_slidermobile0}:
|
|
||||||
company_id: base.main_company
|
|
||||||
name: Slider Mobile
|
|
||||||
product_efficiency: 1.0
|
|
||||||
product_id: my_slider_mobile_0
|
|
||||||
product_qty: 1.0
|
|
||||||
product_uom: product.product_uom_unit
|
|
||||||
product_uos_qty: 0.0
|
|
||||||
sequence: 0.0
|
|
||||||
type: normal
|
|
|
@ -1,68 +0,0 @@
|
||||||
"""
|
|
||||||
Benchmark based on the `sale_mrp` addons (in `sale_mrp/test/sale_mrp.yml`).
|
|
||||||
"""
|
|
||||||
|
|
||||||
import time
|
|
||||||
|
|
||||||
from .benchmarks import Bench
|
|
||||||
|
|
||||||
class BenchSaleMrp(Bench):
|
|
||||||
"""\
|
|
||||||
Similar to `sale_mrp/test/sale_mrp.yml`.
|
|
||||||
|
|
||||||
This benchmarks the OpenERP server `sale_mrp` module by creating and
|
|
||||||
confirming a sale order. As it creates data in the server, it is necessary
|
|
||||||
to ensure unique names for the newly created data. You can use the --seed
|
|
||||||
argument to give a lower bound to those names. (The number of generated
|
|
||||||
names is --jobs * --samples.)
|
|
||||||
"""
|
|
||||||
|
|
||||||
command_name = 'bench-sale-mrp'
|
|
||||||
bench_name = '`sale_mrp/test/sale_mrp.yml`'
|
|
||||||
|
|
||||||
def measure_once(self, i):
|
|
||||||
if self.worker >= 0:
|
|
||||||
i = int(self.args.seed) + i + (self.worker * int(self.args.samples))
|
|
||||||
else:
|
|
||||||
i = int(self.args.seed) + i
|
|
||||||
|
|
||||||
# Resolve a few external-ids (this has little impact on the running
|
|
||||||
# time of the whole method).
|
|
||||||
product_uom_unit = self.execute('ir.model.data', 'get_object_reference', 'product', 'product_uom_unit')[1]
|
|
||||||
my_slider_mobile_0 = self.execute('ir.model.data', 'get_object_reference', 'bench_sale_mrp', 'my_slider_mobile_0')[1]
|
|
||||||
res_partner_4 = self.execute('ir.model.data', 'get_object_reference', 'base', 'res_partner_4')[1]
|
|
||||||
res_partner_address_7 = self.execute('ir.model.data', 'get_object_reference', 'base', 'res_partner_address_7')[1]
|
|
||||||
list0 = self.execute('ir.model.data', 'get_object_reference', 'product', 'list0')[1]
|
|
||||||
shop = self.execute('ir.model.data', 'get_object_reference', 'sale', 'shop')[1]
|
|
||||||
|
|
||||||
# Create a sale order for the product `Slider Mobile`.
|
|
||||||
data = {
|
|
||||||
'client_order_ref': 'ref_xxx_' + str(i).rjust(6, '0'),
|
|
||||||
'date_order': time.strftime('%Y-%m-%d'),
|
|
||||||
'invoice_quantity': 'order',
|
|
||||||
'name': 'sale_order_ref_xxx_' + str(i).rjust(6, '0'),
|
|
||||||
'order_line': [(0, 0, {
|
|
||||||
'name': 'Slider Mobile',
|
|
||||||
'price_unit': 2,
|
|
||||||
'product_uom': product_uom_unit,
|
|
||||||
'product_uom_qty': 5.0,
|
|
||||||
'state': 'draft',
|
|
||||||
'delay': 7.0,
|
|
||||||
'product_id': my_slider_mobile_0,
|
|
||||||
'product_uos_qty': 5,
|
|
||||||
'type': 'make_to_order',
|
|
||||||
})],
|
|
||||||
'order_policy': 'manual',
|
|
||||||
'partner_id': res_partner_4,
|
|
||||||
'partner_invoice_id': res_partner_address_7,
|
|
||||||
'partner_order_id': res_partner_address_7,
|
|
||||||
'partner_shipping_id': res_partner_address_7,
|
|
||||||
'picking_policy': 'direct',
|
|
||||||
'pricelist_id': list0,
|
|
||||||
'shop_id': shop,
|
|
||||||
}
|
|
||||||
sale_order_id = self.execute('sale.order', 'create', data, {})
|
|
||||||
|
|
||||||
# Confirm the sale order.
|
|
||||||
self.object_proxy.exec_workflow(self.database, self.uid, self.password, 'sale.order', 'order_confirm', sale_order_id, {})
|
|
||||||
|
|
|
@ -1,166 +0,0 @@
|
||||||
"""
|
|
||||||
Define a base class for client-side benchmarking.
|
|
||||||
"""
|
|
||||||
import hashlib
|
|
||||||
import multiprocessing
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
|
|
||||||
from .client import Client
|
|
||||||
|
|
||||||
class Bench(Client):
|
|
||||||
"""
|
|
||||||
Base class for concurrent benchmarks. The measure_once() method must be
|
|
||||||
overriden.
|
|
||||||
|
|
||||||
Each sub-benchmark will be run in its own process then a report is done
|
|
||||||
with all the results (shared with the main process using a
|
|
||||||
`multiprocessing.Array`).
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, subparsers=None):
|
|
||||||
super(Bench, self).__init__(subparsers)
|
|
||||||
self.parser.add_argument('-n', '--samples', metavar='INT',
|
|
||||||
default=100, help='number of measurements to take')
|
|
||||||
# TODO if -n <int>s is given (instead of -n <int>), run the
|
|
||||||
# benchmark for <int> seconds and return the number of iterations.
|
|
||||||
self.parser.add_argument('-o', '--output', metavar='PATH',
|
|
||||||
required=True, help='path to save the generated report')
|
|
||||||
self.parser.add_argument('--append', action='store_true',
|
|
||||||
default=False, help='append the report to an existing file')
|
|
||||||
self.parser.add_argument('-j', '--jobs', metavar='JOBS',
|
|
||||||
default=1, help='number of concurrent workers')
|
|
||||||
self.parser.add_argument('--seed', metavar='SEED',
|
|
||||||
default=0, help='a value to ensure different runs can create unique data')
|
|
||||||
self.worker = -1
|
|
||||||
|
|
||||||
def work(self, iarr=None):
|
|
||||||
if iarr:
|
|
||||||
# If an array is given, it means we are a worker process...
|
|
||||||
self.work_slave(iarr)
|
|
||||||
else:
|
|
||||||
# ... else we are the main process and we will spawn workers,
|
|
||||||
# passing them an array.
|
|
||||||
self.work_master()
|
|
||||||
|
|
||||||
def work_master(self):
|
|
||||||
N = int(self.args.samples)
|
|
||||||
self.arrs = [(i, multiprocessing.Array('f', range(N)))
|
|
||||||
for i in xrange(int(self.args.jobs))]
|
|
||||||
ps = [multiprocessing.Process(target=self.run, args=(arr,))
|
|
||||||
for arr in self.arrs]
|
|
||||||
[p.start() for p in ps]
|
|
||||||
[p.join() for p in ps]
|
|
||||||
|
|
||||||
self.report_html()
|
|
||||||
|
|
||||||
def work_slave(self, iarr):
|
|
||||||
j, arr = iarr
|
|
||||||
self.worker = j
|
|
||||||
N = int(self.args.samples)
|
|
||||||
total_t0 = time.time()
|
|
||||||
for i in xrange(N):
|
|
||||||
t0 = time.time()
|
|
||||||
self.measure_once(i)
|
|
||||||
t1 = time.time()
|
|
||||||
arr[i] = t1 - t0
|
|
||||||
print >> sys.stdout, '\r%s' % ('|' * (i * 60 / N)),
|
|
||||||
print >> sys.stdout, '%s %s%%' % \
|
|
||||||
(' ' * (60 - (i * 60 / N)), int(float(i+1)/N*100)),
|
|
||||||
sys.stdout.flush()
|
|
||||||
total_t1 = time.time()
|
|
||||||
print '\nDone in %ss.' % (total_t1 - total_t0)
|
|
||||||
|
|
||||||
def report_html(self):
|
|
||||||
series = []
|
|
||||||
for arr in self.arrs:
|
|
||||||
serie = """{
|
|
||||||
data: %s,
|
|
||||||
points: { show: true }
|
|
||||||
}""" % ([[x, i] for i, x in enumerate(arr)],)
|
|
||||||
series.append(serie)
|
|
||||||
chart_id = hashlib.md5(" ".join(sys.argv)).hexdigest()
|
|
||||||
HEADER = """<!doctype html>
|
|
||||||
<title>Benchmarks</title>
|
|
||||||
<meta charset=utf-8>
|
|
||||||
<script type="text/javascript" src="js/jquery.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/jquery.flot.js"></script>
|
|
||||||
"""
|
|
||||||
|
|
||||||
CONTENT = """<h1>%s</h1>
|
|
||||||
%s
|
|
||||||
<div id='chart_%s' style='width:400px;height:300px;'>...</div>
|
|
||||||
<script type="text/javascript">
|
|
||||||
$.plot($("#chart_%s"), [%s],
|
|
||||||
{yaxis: { ticks: false }});
|
|
||||||
</script>""" % (self.bench_name, ' '.join(sys.argv), chart_id, chart_id,
|
|
||||||
','.join(series))
|
|
||||||
if self.args.append:
|
|
||||||
with open(self.args.output, 'a') as f:
|
|
||||||
f.write(CONTENT,)
|
|
||||||
else:
|
|
||||||
with open(self.args.output, 'w') as f:
|
|
||||||
f.write(HEADER + CONTENT,)
|
|
||||||
|
|
||||||
def measure_once(self, i):
|
|
||||||
"""
|
|
||||||
The `measure_once` method is called --jobs times. A `i` argument is
|
|
||||||
supplied to allow to create unique values for each execution (e.g. to
|
|
||||||
supply fresh identifiers to a `create` method.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
class BenchRead(Bench):
|
|
||||||
"""Read a record repeatedly."""
|
|
||||||
|
|
||||||
command_name = 'bench-read'
|
|
||||||
bench_name = 'res.users.read(1)'
|
|
||||||
|
|
||||||
def __init__(self, subparsers=None):
|
|
||||||
super(BenchRead, self).__init__(subparsers)
|
|
||||||
self.parser.add_argument('-m', '--model', metavar='MODEL',
|
|
||||||
required=True, help='the model')
|
|
||||||
self.parser.add_argument('-i', '--id', metavar='RECORDID',
|
|
||||||
required=True, help='the record id')
|
|
||||||
|
|
||||||
def measure_once(self, i):
|
|
||||||
self.execute(self.args.model, 'read', [self.args.id], [])
|
|
||||||
|
|
||||||
class BenchFieldsViewGet(Bench):
|
|
||||||
"""Read a record's fields and view architecture repeatedly."""
|
|
||||||
|
|
||||||
command_name = 'bench-view'
|
|
||||||
bench_name = 'res.users.fields_view_get(1)'
|
|
||||||
|
|
||||||
def __init__(self, subparsers=None):
|
|
||||||
super(BenchFieldsViewGet, self).__init__(subparsers)
|
|
||||||
self.parser.add_argument('-m', '--model', metavar='MODEL',
|
|
||||||
required=True, help='the model')
|
|
||||||
self.parser.add_argument('-i', '--id', metavar='RECORDID',
|
|
||||||
required=True, help='the record id')
|
|
||||||
|
|
||||||
def measure_once(self, i):
|
|
||||||
self.execute(self.args.model, 'fields_view_get', self.args.id)
|
|
||||||
|
|
||||||
class BenchDummy(Bench):
|
|
||||||
"""Dummy (call test.limits.model.consume_nothing())."""
|
|
||||||
|
|
||||||
command_name = 'bench-dummy'
|
|
||||||
bench_name = 'test.limits.model.consume_nothing()'
|
|
||||||
|
|
||||||
def __init__(self, subparsers=None):
|
|
||||||
super(BenchDummy, self).__init__(subparsers)
|
|
||||||
self.parser.add_argument('-a', '--args', metavar='ARGS',
|
|
||||||
default='', help='some arguments to serialize')
|
|
||||||
|
|
||||||
def measure_once(self, i):
|
|
||||||
self.execute('test.limits.model', 'consume_nothing')
|
|
||||||
|
|
||||||
class BenchLogin(Bench):
|
|
||||||
"""Login (update res_users.date)."""
|
|
||||||
|
|
||||||
command_name = 'bench-login'
|
|
||||||
bench_name = 'res.users.login(1)'
|
|
||||||
|
|
||||||
def measure_once(self, i):
|
|
||||||
self.common_proxy.login(self.database, self.user, self.password)
|
|
|
@ -1,44 +0,0 @@
|
||||||
"""
|
|
||||||
Call an arbitrary model's method.
|
|
||||||
"""
|
|
||||||
import ast
|
|
||||||
import os
|
|
||||||
import pprint
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import xmlrpclib
|
|
||||||
|
|
||||||
import client
|
|
||||||
|
|
||||||
class Call(client.Client):
|
|
||||||
"""\
|
|
||||||
Call an arbitrary model's method.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
> oe call res.users.read '[1, 3]' '[]' -u 1 -p admin
|
|
||||||
"""
|
|
||||||
# TODO The above docstring is completely borked in the
|
|
||||||
# --help message.
|
|
||||||
|
|
||||||
command_name = 'call'
|
|
||||||
|
|
||||||
def __init__(self, subparsers=None):
|
|
||||||
super(Call, self).__init__(subparsers)
|
|
||||||
self.parser.add_argument('call', metavar='MODEL.METHOD',
|
|
||||||
help='the model and the method to call, using the '
|
|
||||||
'<model>.<method> format.')
|
|
||||||
self.parser.add_argument('args', metavar='ARGUMENT',
|
|
||||||
nargs='+',
|
|
||||||
help='the argument for the method call, must be '
|
|
||||||
'`ast.literal_eval` compatible. Can be repeated.')
|
|
||||||
|
|
||||||
def work(self):
|
|
||||||
try:
|
|
||||||
model, method = self.args.call.rsplit('.', 1)
|
|
||||||
except:
|
|
||||||
print "Invalid syntax `%s` must have the form <model>.<method>."
|
|
||||||
sys.exit(1)
|
|
||||||
args = tuple(map(ast.literal_eval, self.args.args)) if self.args.args else ()
|
|
||||||
x = self.execute(model, method, *args)
|
|
||||||
pprint.pprint(x, indent=4)
|
|
||||||
|
|
|
@ -1,137 +0,0 @@
|
||||||
"""
|
|
||||||
Define a few common arguments for client-side command-line tools.
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import xmlrpclib
|
|
||||||
|
|
||||||
import common
|
|
||||||
|
|
||||||
class Client(common.Command):
|
|
||||||
"""
|
|
||||||
Base class for XML-RPC command-line clients. It must be inherited and the
|
|
||||||
work() method overriden.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, subparsers=None):
|
|
||||||
super(Client, self).__init__(subparsers)
|
|
||||||
required_or_default = common.required_or_default
|
|
||||||
self.parser.add_argument('-H', '--host', metavar='HOST',
|
|
||||||
**required_or_default('HOST', 'the server host'))
|
|
||||||
self.parser.add_argument('-P', '--port', metavar='PORT',
|
|
||||||
**required_or_default('PORT', 'the server port'))
|
|
||||||
|
|
||||||
def execute(self, *args):
|
|
||||||
return self.object_proxy.execute(self.database, self.uid, self.password, *args)
|
|
||||||
|
|
||||||
def initialize(self):
|
|
||||||
self.host = self.args.host
|
|
||||||
self.port = int(self.args.port)
|
|
||||||
self.database = self.args.database
|
|
||||||
self.user = self.args.user
|
|
||||||
self.password = self.args.password
|
|
||||||
|
|
||||||
self.url = 'http://%s:%d/xmlrpc/' % (self.host, self.port)
|
|
||||||
self.common_proxy = xmlrpclib.ServerProxy(self.url + 'common')
|
|
||||||
self.object_proxy = xmlrpclib.ServerProxy(self.url + 'object')
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.uid = int(self.user)
|
|
||||||
except ValueError, e:
|
|
||||||
self.uid = self.common_proxy.login(self.database, self.user, self.password)
|
|
||||||
|
|
||||||
def run(self, *args):
|
|
||||||
self.initialize()
|
|
||||||
self.work(*args)
|
|
||||||
|
|
||||||
def work(self, *args):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Open(Client):
|
|
||||||
"""Get the web client's URL to view a specific model."""
|
|
||||||
|
|
||||||
command_name = 'open'
|
|
||||||
|
|
||||||
def __init__(self, subparsers=None):
|
|
||||||
super(Open, self).__init__(subparsers)
|
|
||||||
self.parser.add_argument('-m', '--model', metavar='MODEL',
|
|
||||||
required=True, help='the view type')
|
|
||||||
self.parser.add_argument('-v', '--view-mode', metavar='VIEWMODE',
|
|
||||||
default='tree', help='the view mode')
|
|
||||||
|
|
||||||
def work(self):
|
|
||||||
ids = self.execute('ir.actions.act_window', 'search', [
|
|
||||||
('res_model', '=', self.args.model),
|
|
||||||
('view_mode', 'like', self.args.view_mode),
|
|
||||||
])
|
|
||||||
xs = self.execute('ir.actions.act_window', 'read', ids, [])
|
|
||||||
for x in xs:
|
|
||||||
print x['id'], x['name']
|
|
||||||
d = {}
|
|
||||||
d['host'] = self.host
|
|
||||||
d['port'] = self.port
|
|
||||||
d['action_id'] = x['id']
|
|
||||||
print " http://%(host)s:%(port)s/web/webclient/home#action_id=%(action_id)s" % d
|
|
||||||
|
|
||||||
class Show(Client):
|
|
||||||
"""Display a record."""
|
|
||||||
|
|
||||||
command_name = 'show'
|
|
||||||
|
|
||||||
def __init__(self, subparsers=None):
|
|
||||||
super(Show, self).__init__(subparsers)
|
|
||||||
self.parser.add_argument('-m', '--model', metavar='MODEL',
|
|
||||||
required=True, help='the model')
|
|
||||||
self.parser.add_argument('-i', '--id', metavar='RECORDID',
|
|
||||||
required=True, help='the record id')
|
|
||||||
|
|
||||||
def work(self):
|
|
||||||
xs = self.execute(self.args.model, 'read', [self.args.id], [])
|
|
||||||
if xs:
|
|
||||||
x = xs[0]
|
|
||||||
print x['name']
|
|
||||||
else:
|
|
||||||
print "Record not found."
|
|
||||||
|
|
||||||
class ConsumeNothing(Client):
|
|
||||||
"""Call test.limits.model.consume_nothing()."""
|
|
||||||
|
|
||||||
command_name = 'consume-nothing'
|
|
||||||
|
|
||||||
def work(self):
|
|
||||||
xs = self.execute('test.limits.model', 'consume_nothing')
|
|
||||||
|
|
||||||
class ConsumeMemory(Client):
|
|
||||||
"""Call test.limits.model.consume_memory()."""
|
|
||||||
|
|
||||||
command_name = 'consume-memory'
|
|
||||||
|
|
||||||
def __init__(self, subparsers=None):
|
|
||||||
super(ConsumeMemory, self).__init__(subparsers)
|
|
||||||
self.parser.add_argument('--size', metavar='SIZE',
|
|
||||||
required=True, help='size of the list to allocate')
|
|
||||||
|
|
||||||
def work(self):
|
|
||||||
xs = self.execute('test.limits.model', 'consume_memory', int(self.args.size))
|
|
||||||
|
|
||||||
class LeakMemory(ConsumeMemory):
|
|
||||||
"""Call test.limits.model.leak_memory()."""
|
|
||||||
|
|
||||||
command_name = 'leak-memory'
|
|
||||||
|
|
||||||
def work(self):
|
|
||||||
xs = self.execute('test.limits.model', 'leak_memory', int(self.args.size))
|
|
||||||
|
|
||||||
class ConsumeCPU(Client):
|
|
||||||
"""Call test.limits.model.consume_cpu_time()."""
|
|
||||||
|
|
||||||
command_name = 'consume-cpu'
|
|
||||||
|
|
||||||
def __init__(self, subparsers=None):
|
|
||||||
super(ConsumeCPU, self).__init__(subparsers)
|
|
||||||
self.parser.add_argument('--seconds', metavar='INT',
|
|
||||||
required=True, help='how much CPU time to consume')
|
|
||||||
|
|
||||||
def work(self):
|
|
||||||
xs = self.execute('test.limits.model', 'consume_cpu_time', int(self.args.seconds))
|
|
|
@ -1,108 +0,0 @@
|
||||||
"""
|
|
||||||
Define a few common arguments for server-side command-line tools.
|
|
||||||
"""
|
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
try:
|
|
||||||
from setproctitle import setproctitle
|
|
||||||
except ImportError:
|
|
||||||
setproctitle = lambda x: None
|
|
||||||
import sys
|
|
||||||
|
|
||||||
def add_addons_argument(parser):
|
|
||||||
"""
|
|
||||||
Add a common --addons argument to a parser.
|
|
||||||
"""
|
|
||||||
parser.add_argument('--addons', metavar='ADDONS',
|
|
||||||
**required_or_default('ADDONS',
|
|
||||||
'colon-separated list of paths to addons'))
|
|
||||||
def set_addons(args):
|
|
||||||
"""
|
|
||||||
Turn args.addons into a list instead of a column-separated strings.
|
|
||||||
Set openerp.toools.config accordingly.
|
|
||||||
"""
|
|
||||||
import openerp.tools.config
|
|
||||||
config = openerp.tools.config
|
|
||||||
|
|
||||||
assert hasattr(args, 'addons')
|
|
||||||
if args.addons:
|
|
||||||
args.addons = args.addons.split(':')
|
|
||||||
else:
|
|
||||||
args.addons = []
|
|
||||||
|
|
||||||
config['addons_path'] = ','.join(args.addons)
|
|
||||||
|
|
||||||
def get_addons_from_paths(paths, exclude):
|
|
||||||
"""
|
|
||||||
Build a list of available modules from a list of addons paths.
|
|
||||||
"""
|
|
||||||
exclude = exclude or []
|
|
||||||
module_names = []
|
|
||||||
for p in paths:
|
|
||||||
if os.path.exists(p):
|
|
||||||
names = [n for n in os.listdir(p) if os.path.isfile(os.path.join(p, n, '__openerp__.py')) and not n.startswith('.') and n not in exclude]
|
|
||||||
names = filter(lambda a: os.path.isdir(os.path.join(p, a)), names)
|
|
||||||
names = filter(lambda a: os.path.exists(os.path.join(p, a, '__openerp__.py')), names)
|
|
||||||
module_names.extend(names)
|
|
||||||
else:
|
|
||||||
print "The addons path `%s` doesn't exist." % p
|
|
||||||
sys.exit(1)
|
|
||||||
return module_names
|
|
||||||
|
|
||||||
def required_or_default(name, h):
|
|
||||||
"""
|
|
||||||
Helper to define `argparse` arguments. If the name is the environment,
|
|
||||||
the argument is optional and draw its value from the environment if not
|
|
||||||
supplied on the command-line. If it is not in the environment, make it
|
|
||||||
a mandatory argument.
|
|
||||||
"""
|
|
||||||
if os.environ.get('OPENERP_' + name.upper()):
|
|
||||||
d = {'default': os.environ['OPENERP_' + name.upper()]}
|
|
||||||
else:
|
|
||||||
d = {'required': True}
|
|
||||||
d['help'] = h + '. The environment variable OPENERP_' + \
|
|
||||||
name.upper() + ' can be used instead.'
|
|
||||||
return d
|
|
||||||
|
|
||||||
class Command(object):
|
|
||||||
"""
|
|
||||||
Base class to create command-line tools. It must be inherited and the
|
|
||||||
run() method overriden.
|
|
||||||
"""
|
|
||||||
|
|
||||||
command_name = 'stand-alone'
|
|
||||||
|
|
||||||
def __init__(self, subparsers=None):
|
|
||||||
if subparsers:
|
|
||||||
self.parser = parser = subparsers.add_parser(self.command_name,
|
|
||||||
description=self.__class__.__doc__)
|
|
||||||
else:
|
|
||||||
self.parser = parser = argparse.ArgumentParser(
|
|
||||||
description=self.__class__.__doc__)
|
|
||||||
|
|
||||||
parser.add_argument('-d', '--database', metavar='DATABASE',
|
|
||||||
**required_or_default('DATABASE', 'the database to connect to'))
|
|
||||||
parser.add_argument('-u', '--user', metavar='USER',
|
|
||||||
**required_or_default('USER', 'the user login or ID. When using '
|
|
||||||
'RPC, providing an ID avoid the login() step'))
|
|
||||||
parser.add_argument('-p', '--password', metavar='PASSWORD',
|
|
||||||
**required_or_default('PASSWORD', 'the user password')) # TODO read it from the command line or from file.
|
|
||||||
|
|
||||||
parser.set_defaults(run=self.run_with_args)
|
|
||||||
|
|
||||||
def run_with_args(self, args):
|
|
||||||
self.args = args
|
|
||||||
self.run()
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
print 'Stub Command.run().'
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def stand_alone(cls):
|
|
||||||
"""
|
|
||||||
A single Command object is a complete command-line program. See
|
|
||||||
`openerp-command/stand-alone` for an example.
|
|
||||||
"""
|
|
||||||
command = cls()
|
|
||||||
args = command.parser.parse_args()
|
|
||||||
args.run(args)
|
|
|
@ -1,25 +0,0 @@
|
||||||
"""
|
|
||||||
Display the currently used configuration. The configuration for any
|
|
||||||
sub-command is normally given by options. But some options can be specified
|
|
||||||
using environment variables. This sub-command shows those variables.
|
|
||||||
A `set` sub-command should be provided when the configuration is in a real
|
|
||||||
configuration file instead of environment variables.
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import textwrap
|
|
||||||
|
|
||||||
def run(args):
|
|
||||||
for x in ('database', 'addons', 'host', 'port'):
|
|
||||||
x_ = ('openerp_' + x).upper()
|
|
||||||
if x_ in os.environ:
|
|
||||||
print '%s: %s' % (x, os.environ[x_])
|
|
||||||
else:
|
|
||||||
print '%s: <not set>' % (x, )
|
|
||||||
os.environ['OPENERP_DATABASE'] = 'yeah'
|
|
||||||
|
|
||||||
def add_parser(subparsers):
|
|
||||||
parser = subparsers.add_parser('conf',
|
|
||||||
description='Display the currently used configuration.')
|
|
||||||
|
|
||||||
parser.set_defaults(run=run)
|
|
|
@ -1,46 +0,0 @@
|
||||||
"""
|
|
||||||
Run an OpenERP cron process.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
import common
|
|
||||||
|
|
||||||
def run(args):
|
|
||||||
import openerp
|
|
||||||
import openerp.cli.server
|
|
||||||
import openerp.tools.config
|
|
||||||
import openerp.service.cron
|
|
||||||
config = openerp.tools.config
|
|
||||||
|
|
||||||
os.environ["TZ"] = "UTC"
|
|
||||||
common.set_addons(args)
|
|
||||||
args.database = args.database or []
|
|
||||||
|
|
||||||
config['log_handler'] = [':WARNING', 'openerp.addons.base.ir.ir_cron:DEBUG']
|
|
||||||
|
|
||||||
openerp.multi_process = True
|
|
||||||
common.setproctitle('openerp-cron [%s]' % ', '.join(args.database))
|
|
||||||
|
|
||||||
openerp.cli.server.check_root_user()
|
|
||||||
openerp.netsvc.init_logger()
|
|
||||||
#openerp.cli.server.report_configuration()
|
|
||||||
openerp.cli.server.setup_signal_handlers(openerp.cli.server.signal_handler)
|
|
||||||
import openerp.addons.base
|
|
||||||
if args.database:
|
|
||||||
for db in args.database:
|
|
||||||
openerp.cli.server.preload_registry(db)
|
|
||||||
openerp.service.cron.start_service()
|
|
||||||
openerp.cli.server.quit_on_signals()
|
|
||||||
else:
|
|
||||||
print "No database given."
|
|
||||||
|
|
||||||
|
|
||||||
def add_parser(subparsers):
|
|
||||||
parser = subparsers.add_parser('cron',
|
|
||||||
description='Run an OpenERP cron process.')
|
|
||||||
common.add_addons_argument(parser)
|
|
||||||
parser.add_argument('--database', action='append',
|
|
||||||
help='Database for which cron jobs are processed (can be repeated)')
|
|
||||||
|
|
||||||
parser.set_defaults(run=run)
|
|
|
@ -1,50 +0,0 @@
|
||||||
"""
|
|
||||||
Drop a database.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import common
|
|
||||||
|
|
||||||
# TODO turn template1 in a parameter
|
|
||||||
# This should be exposed from openerp (currently in
|
|
||||||
# openerp/service/web_services.py).
|
|
||||||
def drop_database(database_name):
|
|
||||||
import openerp
|
|
||||||
openerp.netsvc.init_logger()
|
|
||||||
db = openerp.sql_db.db_connect('template1')
|
|
||||||
cr = db.cursor()
|
|
||||||
cr.autocommit(True) # avoid transaction block
|
|
||||||
try:
|
|
||||||
# TODO option for doing this.
|
|
||||||
# Try to terminate all other connections that might prevent
|
|
||||||
# dropping the database
|
|
||||||
|
|
||||||
# PostgreSQL 9.2 renamed pg_stat_activity.procpid to pid:
|
|
||||||
# http://www.postgresql.org/docs/9.2/static/release-9-2.html#AEN110389
|
|
||||||
pid_col = 'pid' if cr._cnx.server_version >= 90200 else 'procpid'
|
|
||||||
try:
|
|
||||||
cr.execute("""SELECT pg_terminate_backend(%(pid_col)s)
|
|
||||||
FROM pg_stat_activity
|
|
||||||
WHERE datname = %%s AND
|
|
||||||
%(pid_col)s != pg_backend_pid()""" % {'pid_col': pid_col},
|
|
||||||
(database_name,))
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
cr.execute('DROP DATABASE "%s"' % database_name, log_exceptions=False)
|
|
||||||
except Exception, e:
|
|
||||||
print "Can't drop %s" % (database_name,)
|
|
||||||
finally:
|
|
||||||
cr.close()
|
|
||||||
|
|
||||||
def run(args):
|
|
||||||
assert args.database
|
|
||||||
drop_database(args.database)
|
|
||||||
|
|
||||||
def add_parser(subparsers):
|
|
||||||
parser = subparsers.add_parser('drop',
|
|
||||||
description='Drop a database.')
|
|
||||||
parser.add_argument('-d', '--database', metavar='DATABASE',
|
|
||||||
**common.required_or_default('DATABASE', 'the database to create'))
|
|
||||||
|
|
||||||
parser.set_defaults(run=run)
|
|
|
@ -1,50 +0,0 @@
|
||||||
"""
|
|
||||||
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)
|
|
|
@ -1,121 +0,0 @@
|
||||||
"""
|
|
||||||
Install OpenERP on a new (by default) database.
|
|
||||||
"""
|
|
||||||
import contextlib
|
|
||||||
import errno
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
|
|
||||||
import common
|
|
||||||
|
|
||||||
# From http://code.activestate.com/recipes/576572/
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def lock_file(path, wait_delay=.1, max_try=600):
|
|
||||||
attempt = 0
|
|
||||||
while True:
|
|
||||||
attempt += 1
|
|
||||||
if attempt > max_try:
|
|
||||||
raise IOError("Could not lock file %s." % path)
|
|
||||||
try:
|
|
||||||
fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR)
|
|
||||||
except OSError, e:
|
|
||||||
if e.errno != errno.EEXIST:
|
|
||||||
raise
|
|
||||||
time.sleep(wait_delay)
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
try:
|
|
||||||
yield fd
|
|
||||||
finally:
|
|
||||||
os.close(fd)
|
|
||||||
os.unlink(path)
|
|
||||||
|
|
||||||
def run(args):
|
|
||||||
assert args.database
|
|
||||||
assert not (args.module and args.all_modules)
|
|
||||||
|
|
||||||
import openerp
|
|
||||||
|
|
||||||
config = openerp.tools.config
|
|
||||||
config['db_name'] = args.database
|
|
||||||
|
|
||||||
if args.tests:
|
|
||||||
config['log_handler'] = [':INFO']
|
|
||||||
config['test_enable'] = True
|
|
||||||
config['without_demo'] = False
|
|
||||||
if args.port:
|
|
||||||
config['xmlrpc_port'] = int(args.port)
|
|
||||||
else:
|
|
||||||
config['log_handler'] = [':CRITICAL']
|
|
||||||
config['test_enable'] = False
|
|
||||||
config['without_demo'] = True
|
|
||||||
|
|
||||||
if args.addons:
|
|
||||||
args.addons = args.addons.split(':')
|
|
||||||
else:
|
|
||||||
args.addons = []
|
|
||||||
config['addons_path'] = ','.join(args.addons)
|
|
||||||
|
|
||||||
if args.all_modules:
|
|
||||||
module_names = common.get_addons_from_paths(args.addons, args.exclude)
|
|
||||||
elif args.module:
|
|
||||||
module_names = args.module
|
|
||||||
else:
|
|
||||||
module_names = ['base']
|
|
||||||
config['init'] = dict.fromkeys(module_names, 1)
|
|
||||||
|
|
||||||
if args.coverage:
|
|
||||||
import coverage
|
|
||||||
# Without the `include` kwarg, coverage generates 'memory:0xXXXXX'
|
|
||||||
# filenames (which do not exist) and cause it to crash. No idea why.
|
|
||||||
cov = coverage.coverage(branch=True, include='*.py')
|
|
||||||
cov.start()
|
|
||||||
openerp.netsvc.init_logger()
|
|
||||||
|
|
||||||
if not args.no_create:
|
|
||||||
with lock_file('/tmp/global_openerp_create_database.lock'):
|
|
||||||
openerp.service.db._create_empty_database(args.database)
|
|
||||||
|
|
||||||
config['workers'] = False
|
|
||||||
|
|
||||||
rc = openerp.service.server.start(preload=[args.database], stop=True)
|
|
||||||
|
|
||||||
if args.coverage:
|
|
||||||
cov.stop()
|
|
||||||
cov.html_report(directory='coverage')
|
|
||||||
# If we wanted the report on stdout:
|
|
||||||
# cov.report()
|
|
||||||
|
|
||||||
sys.exit(rc)
|
|
||||||
|
|
||||||
def add_parser(subparsers):
|
|
||||||
parser = subparsers.add_parser('initialize',
|
|
||||||
description='Create and initialize a new OpenERP database.')
|
|
||||||
parser.add_argument('-d', '--database', metavar='DATABASE',
|
|
||||||
**common.required_or_default('DATABASE', 'the database to create'))
|
|
||||||
common.add_addons_argument(parser)
|
|
||||||
parser.add_argument('-P', '--port', metavar='PORT',
|
|
||||||
**common.required_or_default('PORT', 'the server port'))
|
|
||||||
parser.add_argument('--module', metavar='MODULE', action='append',
|
|
||||||
help='specify a module to install'
|
|
||||||
' (this option can be repeated)')
|
|
||||||
parser.add_argument('--all-modules', action='store_true',
|
|
||||||
help='install all visible modules (not compatible with --module)')
|
|
||||||
parser.add_argument('--no-create', action='store_true',
|
|
||||||
help='do not create the database, only initialize it')
|
|
||||||
parser.add_argument('--exclude', metavar='MODULE', action='append',
|
|
||||||
help='exclude a module from installation'
|
|
||||||
' (this option can be repeated)')
|
|
||||||
parser.add_argument('--tests', action='store_true',
|
|
||||||
help='run the tests as modules are installed'
|
|
||||||
' (use the `run-tests` command to choose specific'
|
|
||||||
' tests to run against an existing database).'
|
|
||||||
' Demo data are installed.')
|
|
||||||
parser.add_argument('--coverage', action='store_true',
|
|
||||||
help='report code coverage (particularly useful with --tests).'
|
|
||||||
' The report is generated in a coverage directory and you can'
|
|
||||||
' then point your browser to coverage/index.html.')
|
|
||||||
|
|
||||||
parser.set_defaults(run=run)
|
|
|
@ -1,7 +0,0 @@
|
||||||
import openerpcommand
|
|
||||||
|
|
||||||
def run():
|
|
||||||
""" Main entry point for the openerp-command tool."""
|
|
||||||
parser = openerpcommand.main_parser()
|
|
||||||
args = parser.parse_args()
|
|
||||||
args.run(args)
|
|
|
@ -1,61 +0,0 @@
|
||||||
"""
|
|
||||||
Display information about a given model.
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import textwrap
|
|
||||||
|
|
||||||
def run(args):
|
|
||||||
assert args.database
|
|
||||||
assert args.model
|
|
||||||
import openerp
|
|
||||||
openerp.tools.config['log_level'] = 100
|
|
||||||
openerp.netsvc.init_logger()
|
|
||||||
registry = openerp.modules.registry.RegistryManager.get(
|
|
||||||
args.database, update_module=False)
|
|
||||||
model = registry[args.model]
|
|
||||||
longest_k = 1
|
|
||||||
longest_string = 1
|
|
||||||
columns = model._columns
|
|
||||||
|
|
||||||
if args.field and args.field not in columns:
|
|
||||||
print "No such field."
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if args.field:
|
|
||||||
columns = { args.field: columns[args.field] }
|
|
||||||
else:
|
|
||||||
print "Fields (model `%s`, database `%s`):" % (args.model, args.database)
|
|
||||||
|
|
||||||
for k, v in columns.items():
|
|
||||||
longest_k = len(k) if longest_k < len(k) else longest_k
|
|
||||||
longest_string = len(v.string) \
|
|
||||||
if longest_string < len(v.string) else longest_string
|
|
||||||
for k, v in sorted(columns.items()):
|
|
||||||
attr = []
|
|
||||||
if v.required:
|
|
||||||
attr.append("Required")
|
|
||||||
if v.readonly:
|
|
||||||
attr.append("Read-only")
|
|
||||||
attr = '/'.join(attr)
|
|
||||||
attr = '(' + attr + ')' if attr else attr
|
|
||||||
if args.verbose:
|
|
||||||
print v.string, '-- ' + k + ', ' + v._type, attr
|
|
||||||
else:
|
|
||||||
print k.ljust(longest_k + 2), v._type, attr
|
|
||||||
if args.verbose and v.help:
|
|
||||||
print textwrap.fill(v.help, initial_indent=' ', subsequent_indent=' ')
|
|
||||||
|
|
||||||
def add_parser(subparsers):
|
|
||||||
parser = subparsers.add_parser('model',
|
|
||||||
description='Display information about a given model for an existing database.')
|
|
||||||
parser.add_argument('-d', '--database', metavar='DATABASE', required=True,
|
|
||||||
help='the database to connect to')
|
|
||||||
parser.add_argument('-m', '--model', metavar='MODEL', required=True,
|
|
||||||
help='the model for which information should be displayed')
|
|
||||||
parser.add_argument('-v', '--verbose', action='store_true',
|
|
||||||
help='display more information')
|
|
||||||
parser.add_argument('-f', '--field', metavar='FIELD',
|
|
||||||
help='display information only for this particular field')
|
|
||||||
|
|
||||||
parser.set_defaults(run=run)
|
|
|
@ -1,65 +0,0 @@
|
||||||
"""
|
|
||||||
Show module information for a given database or from the file-system.
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import textwrap
|
|
||||||
|
|
||||||
from . import common
|
|
||||||
|
|
||||||
# TODO provide a --rpc flag to use XML-RPC (with a specific username) instead
|
|
||||||
# of server-side library.
|
|
||||||
def run(args):
|
|
||||||
assert args.database
|
|
||||||
import openerp
|
|
||||||
|
|
||||||
config = openerp.tools.config
|
|
||||||
config['log_handler'] = [':CRITICAL']
|
|
||||||
if args.addons:
|
|
||||||
args.addons = args.addons.split(':')
|
|
||||||
else:
|
|
||||||
args.addons = []
|
|
||||||
config['addons_path'] = ','.join(args.addons)
|
|
||||||
openerp.netsvc.init_logger()
|
|
||||||
|
|
||||||
if args.filesystem:
|
|
||||||
module_names = common.get_addons_from_paths(args.addons, [])
|
|
||||||
print "Modules (addons path %s):" % (', '.join(args.addons),)
|
|
||||||
for x in sorted(module_names):
|
|
||||||
print x
|
|
||||||
else:
|
|
||||||
registry = openerp.modules.registry.RegistryManager.get(
|
|
||||||
args.database, update_module=False)
|
|
||||||
|
|
||||||
xs = []
|
|
||||||
ir_module_module = registry.get('ir.module.module')
|
|
||||||
with registry.cursor() as cr:
|
|
||||||
ids = ir_module_module.search(cr, openerp.SUPERUSER_ID, [], {})
|
|
||||||
xs = ir_module_module.read(cr, openerp.SUPERUSER_ID, ids, [], {})
|
|
||||||
|
|
||||||
if xs:
|
|
||||||
print "Modules (database `%s`):" % (args.database,)
|
|
||||||
for x in xs:
|
|
||||||
if args.short:
|
|
||||||
print '%3d %s' % (x['id'], x['name'])
|
|
||||||
else:
|
|
||||||
print '%3d %s %s' % (x['id'], x['name'], {'installed': '(installed)'}.get(x['state'], ''))
|
|
||||||
else:
|
|
||||||
print "No module found (database `%s`)." % (args.database,)
|
|
||||||
|
|
||||||
def add_parser(subparsers):
|
|
||||||
parser = subparsers.add_parser('module',
|
|
||||||
description='Display modules known from a given database or on file-system.')
|
|
||||||
parser.add_argument('-d', '--database', metavar='DATABASE',
|
|
||||||
**common.required_or_default('DATABASE', 'the database to modify'))
|
|
||||||
common.add_addons_argument(parser)
|
|
||||||
parser.add_argument('-m', '--module', metavar='MODULE', required=False,
|
|
||||||
help='the module for which information should be shown')
|
|
||||||
parser.add_argument('-v', '--verbose', action='store_true',
|
|
||||||
help='display more information')
|
|
||||||
parser.add_argument('--short', action='store_true',
|
|
||||||
help='display less information')
|
|
||||||
parser.add_argument('-f', '--filesystem', action='store_true',
|
|
||||||
help='display module in the addons path, not in db')
|
|
||||||
|
|
||||||
parser.set_defaults(run=run)
|
|
|
@ -1,89 +0,0 @@
|
||||||
_oe()
|
|
||||||
{
|
|
||||||
local cur prev opts
|
|
||||||
COMPREPLY=()
|
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
|
||||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
|
||||||
|
|
||||||
cmd="${COMP_WORDS[0]}"
|
|
||||||
subcmd=""
|
|
||||||
if [[ ${COMP_CWORD} > 0 ]] ; then
|
|
||||||
subcmd="${COMP_WORDS[1]}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# oe
|
|
||||||
|
|
||||||
opts="initialize model read run-tests scaffold update \
|
|
||||||
call open show consume-nothing consume-memory leak-memory \
|
|
||||||
consume-cpu bench-read bench-fields-view-get bench-dummy bench-login \
|
|
||||||
bench-sale-mrp --help"
|
|
||||||
|
|
||||||
if [[ ${prev} == oe && ${cur} != -* ]] ; then
|
|
||||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# oe call
|
|
||||||
|
|
||||||
opts="--database --user --password --host --port --help"
|
|
||||||
|
|
||||||
if [[ ${subcmd} == call ]] ; then
|
|
||||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# oe initialize
|
|
||||||
|
|
||||||
opts="--database --addons --all-modules --exclude --no-create --help"
|
|
||||||
|
|
||||||
if [[ ${subcmd} == initialize ]] ; then
|
|
||||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# oe model
|
|
||||||
|
|
||||||
opts="--database --model --field --verbose --help"
|
|
||||||
|
|
||||||
if [[ ${subcmd} == model ]] ; then
|
|
||||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# oe read
|
|
||||||
|
|
||||||
opts="--database --model --id --field --verbose --short --help"
|
|
||||||
|
|
||||||
if [[ ${subcmd} == read ]] ; then
|
|
||||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# oe run-tests
|
|
||||||
|
|
||||||
opts="--database --addons --module --dry-run --help"
|
|
||||||
|
|
||||||
if [[ ${subcmd} == run-tests ]] ; then
|
|
||||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# oe scaffold
|
|
||||||
|
|
||||||
opts="--help"
|
|
||||||
|
|
||||||
if [[ ${subcmd} == scaffold ]] ; then
|
|
||||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# fallback for unimplemented completion
|
|
||||||
|
|
||||||
opts="--help"
|
|
||||||
|
|
||||||
if [[ true ]] ; then
|
|
||||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
complete -F _oe oe
|
|
|
@ -1,61 +0,0 @@
|
||||||
"""
|
|
||||||
Read a record.
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import textwrap
|
|
||||||
|
|
||||||
import common
|
|
||||||
|
|
||||||
# TODO provide a --rpc flag to use XML-RPC (with a specific username) instead
|
|
||||||
# of server-side library.
|
|
||||||
def run(args):
|
|
||||||
assert args.database
|
|
||||||
assert args.model
|
|
||||||
import openerp
|
|
||||||
config = openerp.tools.config
|
|
||||||
config['log_handler'] = [':CRITICAL']
|
|
||||||
common.set_addons(args)
|
|
||||||
openerp.netsvc.init_logger()
|
|
||||||
registry = openerp.modules.registry.RegistryManager.get(
|
|
||||||
args.database, update_module=False)
|
|
||||||
model = registry[args.model]
|
|
||||||
field_names = [args.field] if args.field else []
|
|
||||||
if args.short:
|
|
||||||
# ignore --field
|
|
||||||
field_names = ['name']
|
|
||||||
with registry.cursor() as cr:
|
|
||||||
xs = model.read(cr, 1, args.id, field_names, {})
|
|
||||||
|
|
||||||
if xs:
|
|
||||||
print "Records (model `%s`, database `%s`):" % (args.model, args.database)
|
|
||||||
x = xs[0]
|
|
||||||
if args.short:
|
|
||||||
print str(x['id']) + '.', x['name']
|
|
||||||
else:
|
|
||||||
longest_k = 1
|
|
||||||
for k, v in x.items():
|
|
||||||
longest_k = len(k) if longest_k < len(k) else longest_k
|
|
||||||
for k, v in sorted(x.items()):
|
|
||||||
print (k + ':').ljust(longest_k + 2), v
|
|
||||||
else:
|
|
||||||
print "Record not found."
|
|
||||||
|
|
||||||
def add_parser(subparsers):
|
|
||||||
parser = subparsers.add_parser('read',
|
|
||||||
description='Display a record.')
|
|
||||||
parser.add_argument('-d', '--database', metavar='DATABASE',
|
|
||||||
**common.required_or_default('DATABASE', 'the database to connect to'))
|
|
||||||
common.add_addons_argument(parser)
|
|
||||||
parser.add_argument('-m', '--model', metavar='MODEL', required=True,
|
|
||||||
help='the model for which a record should be read')
|
|
||||||
parser.add_argument('-i', '--id', metavar='RECORDID', required=True,
|
|
||||||
help='the record id')
|
|
||||||
parser.add_argument('-v', '--verbose', action='store_true',
|
|
||||||
help='display more information')
|
|
||||||
parser.add_argument('--short', action='store_true',
|
|
||||||
help='display less information')
|
|
||||||
parser.add_argument('-f', '--field', metavar='FIELD',
|
|
||||||
help='display information only for this particular field')
|
|
||||||
|
|
||||||
parser.set_defaults(run=run)
|
|
|
@ -1,131 +0,0 @@
|
||||||
"""
|
|
||||||
Generate an OpenERP module skeleton.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import functools
|
|
||||||
import keyword
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import jinja2
|
|
||||||
|
|
||||||
# FIXME: add logging
|
|
||||||
def run(args):
|
|
||||||
env = jinja2.Environment(loader=jinja2.PackageLoader(
|
|
||||||
'openerpcommand', 'templates'))
|
|
||||||
env.filters['snake'] = snake
|
|
||||||
args.dependency = 'web' if args.controller else 'base'
|
|
||||||
|
|
||||||
module_name = snake(args.module)
|
|
||||||
module = functools.partial(
|
|
||||||
os.path.join, args.modules_dir, module_name)
|
|
||||||
|
|
||||||
if args.controller is True:
|
|
||||||
args.controller = module_name
|
|
||||||
|
|
||||||
if args.model is True:
|
|
||||||
args.model = module_name
|
|
||||||
|
|
||||||
if os.path.exists(module()):
|
|
||||||
message = "The path `%s` already exists." % module()
|
|
||||||
die(message)
|
|
||||||
|
|
||||||
dump(env, '__openerp__.jinja2', module('__openerp__.py'), config=args)
|
|
||||||
dump(env, '__init__.jinja2', module('__init__.py'), modules=[
|
|
||||||
args.controller and 'controllers',
|
|
||||||
args.model and 'models'
|
|
||||||
])
|
|
||||||
dump(env, 'ir.model.access.jinja2', module('security', 'ir.model.access.csv'), config=args)
|
|
||||||
|
|
||||||
if args.controller:
|
|
||||||
controller_module = snake(args.controller)
|
|
||||||
dump(env, '__init__.jinja2', module('controllers', '__init__.py'), modules=[controller_module])
|
|
||||||
dump(env, 'controllers.jinja2',
|
|
||||||
module('controllers', '%s.py' % controller_module),
|
|
||||||
config=args)
|
|
||||||
|
|
||||||
if args.model:
|
|
||||||
model_module = snake(args.model)
|
|
||||||
dump(env, '__init__.jinja2', module('models', '__init__.py'), modules=[model_module])
|
|
||||||
dump(env, 'models.jinja2', module('models', '%s.py' % model_module), config=args)
|
|
||||||
|
|
||||||
def add_parser(subparsers):
|
|
||||||
parser = subparsers.add_parser('scaffold',
|
|
||||||
description='Generate an OpenERP module skeleton.')
|
|
||||||
parser.add_argument('module', metavar='MODULE',
|
|
||||||
help='the name of the generated module')
|
|
||||||
parser.add_argument('modules_dir', metavar='DIRECTORY', type=directory,
|
|
||||||
help="Modules directory in which the new module should be generated")
|
|
||||||
|
|
||||||
controller = parser.add_mutually_exclusive_group()
|
|
||||||
controller.add_argument('--controller', type=identifier,
|
|
||||||
help="The name of the controller to generate")
|
|
||||||
controller.add_argument('--no-controller', dest='controller',
|
|
||||||
action='store_const', const=None, help="Do not generate a controller")
|
|
||||||
|
|
||||||
model = parser.add_mutually_exclusive_group()
|
|
||||||
model.add_argument('--model', type=identifier,
|
|
||||||
help="The name of the model to generate")
|
|
||||||
model.add_argument('--no-model', dest='model',
|
|
||||||
action='store_const', const=None, help="Do not generate a model")
|
|
||||||
|
|
||||||
mod = parser.add_argument_group("Module information",
|
|
||||||
"these are added to the module metadata and displayed on e.g. "
|
|
||||||
"apps.openerp.com. For company-backed modules, the company "
|
|
||||||
"information should be used")
|
|
||||||
mod.add_argument('--name', dest='author_name', default="",
|
|
||||||
help="Name of the module author")
|
|
||||||
mod.add_argument('--website', dest='author_website', default="",
|
|
||||||
help="Website of the module author")
|
|
||||||
mod.add_argument('--category', default="Uncategorized",
|
|
||||||
help="Broad categories to which the module belongs, used for "
|
|
||||||
"filtering within OpenERP and on apps.openerp.com."
|
|
||||||
"Defaults to %(default)s")
|
|
||||||
mod.add_argument('--summary', default="",
|
|
||||||
help="Short (1 phrase/line) summary of the module's purpose, used as "
|
|
||||||
"subtitle on modules listing or apps.openerp.com")
|
|
||||||
|
|
||||||
parser.set_defaults(run=run, controller=True, model=True)
|
|
||||||
|
|
||||||
def snake(s):
|
|
||||||
""" snake cases ``s``
|
|
||||||
|
|
||||||
:param str s:
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
# insert a space before each uppercase character preceded by a
|
|
||||||
# non-uppercase letter
|
|
||||||
s = re.sub(r'(?<=[^A-Z])\B([A-Z])', r' \1', s)
|
|
||||||
# lowercase everything, split on whitespace and join
|
|
||||||
return '_'.join(s.lower().split())
|
|
||||||
|
|
||||||
def dump(env, template, dest, **kwargs):
|
|
||||||
outdir = os.path.dirname(dest)
|
|
||||||
if not os.path.exists(outdir):
|
|
||||||
os.makedirs(outdir)
|
|
||||||
env.get_template(template).stream(**kwargs).dump(dest)
|
|
||||||
# add trailing newline which jinja removes
|
|
||||||
with open(dest, 'a') as f:
|
|
||||||
f.write('\n')
|
|
||||||
|
|
||||||
def identifier(s):
|
|
||||||
if keyword.iskeyword(s):
|
|
||||||
die("%s is a Python keyword and can not be used as a name" % s)
|
|
||||||
if not re.match('[A-Za-z_][A-Za-z0-9_]*', s):
|
|
||||||
die("%s is not a valid Python identifier" % s)
|
|
||||||
return s
|
|
||||||
|
|
||||||
def directory(p):
|
|
||||||
expanded = os.path.abspath(
|
|
||||||
os.path.expanduser(
|
|
||||||
os.path.expandvars(p)))
|
|
||||||
if not os.path.exists(expanded):
|
|
||||||
os.makedirs(expanded)
|
|
||||||
if not os.path.isdir(expanded):
|
|
||||||
die("%s exists but is not a directory" % p)
|
|
||||||
return expanded
|
|
||||||
|
|
||||||
def die(message, code=1):
|
|
||||||
print >>sys.stderr, message
|
|
||||||
sys.exit(code)
|
|
|
@ -1,4 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
{% for module in modules if module -%}
|
|
||||||
import {{ module }}
|
|
||||||
{% endfor %}
|
|
|
@ -1,24 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
{
|
|
||||||
'name': "{{ config.module }}",
|
|
||||||
# short description, used as subtitles on modules listings
|
|
||||||
'summary': "{{ config.summary }}",
|
|
||||||
# long description of module purpose
|
|
||||||
'description': """
|
|
||||||
""",
|
|
||||||
# Who you are
|
|
||||||
'author': "{{ config.author_name }}",
|
|
||||||
'website': "{{ config.author_website }}",
|
|
||||||
|
|
||||||
# categories can be used to filter modules in modules listing
|
|
||||||
'category': '{{ config.category }}',
|
|
||||||
'version': '0.1',
|
|
||||||
|
|
||||||
# any module necessary for this one to work correctly
|
|
||||||
'depends': ['{{ config.dependency }}'],
|
|
||||||
'data': [
|
|
||||||
{{- "'security/ir.model.access.csv'" if config.model -}}
|
|
||||||
],
|
|
||||||
'tests': [
|
|
||||||
],
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from openerp import http
|
|
||||||
from openerp.addons.web.controllers import main
|
|
||||||
|
|
||||||
class {{ config.controller }}(main.Home):
|
|
||||||
@http.route('/', auth='none')
|
|
||||||
def index(self):
|
|
||||||
return "Hello, world!"
|
|
|
@ -1,6 +0,0 @@
|
||||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
|
||||||
{% if config.model -%}
|
|
||||||
access_{{ config.module|snake }}_{{ config.model|snake }},{{- '' -}}
|
|
||||||
access_{{ config.module|snake }}_{{ config.model|snake }},{{- '' -}}
|
|
||||||
model_{{ config.module|snake }}_{{ config.model|snake }},,1,0,0,0
|
|
||||||
{%- endif %}
|
|
|
@ -1,9 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from openerp.osv import orm, fields
|
|
||||||
|
|
||||||
class {{ config.model }}(orm.Model):
|
|
||||||
_name = "{{ config.module|snake }}.{{ config.model|snake }}"
|
|
||||||
|
|
||||||
_columns = {
|
|
||||||
'name': fields.char(),
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
"""
|
|
||||||
Install OpenERP on a new (by default) database.
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import common
|
|
||||||
|
|
||||||
# TODO turn template1 in a parameter
|
|
||||||
# This should be exposed from openerp (currently in
|
|
||||||
# openerp/service/web_services.py).
|
|
||||||
def create_database(database_name):
|
|
||||||
import openerp
|
|
||||||
db = openerp.sql_db.db_connect('template1')
|
|
||||||
cr = db.cursor() # TODO `with db as cr:`
|
|
||||||
try:
|
|
||||||
cr.autocommit(True)
|
|
||||||
cr.execute("""CREATE DATABASE "%s"
|
|
||||||
ENCODING 'unicode' TEMPLATE "template1" """ \
|
|
||||||
% (database_name,))
|
|
||||||
finally:
|
|
||||||
cr.close()
|
|
||||||
|
|
||||||
def run(args):
|
|
||||||
assert args.database
|
|
||||||
assert args.module
|
|
||||||
|
|
||||||
import openerp
|
|
||||||
|
|
||||||
config = openerp.tools.config
|
|
||||||
config['log_handler'] = [':CRITICAL']
|
|
||||||
if args.addons:
|
|
||||||
args.addons = args.addons.split(':')
|
|
||||||
else:
|
|
||||||
args.addons = []
|
|
||||||
config['addons_path'] = ','.join(args.addons)
|
|
||||||
openerp.netsvc.init_logger()
|
|
||||||
|
|
||||||
# Install the import hook, to import openerp.addons.<module>.
|
|
||||||
openerp.modules.module.initialize_sys_path()
|
|
||||||
|
|
||||||
registry = openerp.modules.registry.RegistryManager.get(
|
|
||||||
args.database, update_module=False)
|
|
||||||
|
|
||||||
ir_module_module = registry.get('ir.module.module')
|
|
||||||
with registry.cursor() as cr:
|
|
||||||
ids = ir_module_module.search(cr, openerp.SUPERUSER_ID, [('name', 'in', args.module), ('state', '=', 'installed')], {})
|
|
||||||
if len(ids) == len(args.module):
|
|
||||||
ir_module_module.button_immediate_uninstall(cr, openerp.SUPERUSER_ID, ids, {})
|
|
||||||
else:
|
|
||||||
print "At least one module not found (database `%s`)." % (args.database,)
|
|
||||||
|
|
||||||
def add_parser(subparsers):
|
|
||||||
parser = subparsers.add_parser('uninstall',
|
|
||||||
description='Uninstall some modules from an OpenERP database.')
|
|
||||||
parser.add_argument('-d', '--database', metavar='DATABASE',
|
|
||||||
**common.required_or_default('DATABASE', 'the database to modify'))
|
|
||||||
common.add_addons_argument(parser)
|
|
||||||
parser.add_argument('--module', metavar='MODULE', action='append',
|
|
||||||
help='specify a module to uninstall'
|
|
||||||
' (this option can be repeated)')
|
|
||||||
|
|
||||||
parser.set_defaults(run=run)
|
|
|
@ -1,19 +0,0 @@
|
||||||
"""
|
|
||||||
Update an existing OpenERP database.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def run(args):
|
|
||||||
assert args.database
|
|
||||||
import openerp
|
|
||||||
config = openerp.tools.config
|
|
||||||
config['update']['all'] = 1
|
|
||||||
openerp.modules.registry.RegistryManager.get(
|
|
||||||
args.database, update_module=True)
|
|
||||||
|
|
||||||
def add_parser(subparsers):
|
|
||||||
parser = subparsers.add_parser('update',
|
|
||||||
description='Update an existing OpenERP database.')
|
|
||||||
parser.add_argument('-d', '--database', metavar='DATABASE', required=True,
|
|
||||||
help='the database to update')
|
|
||||||
|
|
||||||
parser.set_defaults(run=run)
|
|
|
@ -1,93 +0,0 @@
|
||||||
"""
|
|
||||||
Run a normal OpenERP HTTP process.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import signal
|
|
||||||
|
|
||||||
import common
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
def mk_signal_handler(server):
|
|
||||||
def signal_handler(sig, frame):
|
|
||||||
"""
|
|
||||||
Specialized signal handler for the evented process.
|
|
||||||
"""
|
|
||||||
print "\n\n\nStopping gevent HTTP server...\n\n\n"
|
|
||||||
server.stop()
|
|
||||||
return signal_handler
|
|
||||||
|
|
||||||
def setup_signal_handlers(signal_handler):
|
|
||||||
SIGNALS = (signal.SIGINT, signal.SIGTERM)
|
|
||||||
map(lambda sig: signal.signal(sig, signal_handler), SIGNALS)
|
|
||||||
|
|
||||||
def run(args):
|
|
||||||
# Note that gevent monkey patching must be done before importing the
|
|
||||||
# `threading` module, see http://stackoverflow.com/questions/8774958/.
|
|
||||||
if args.gevent:
|
|
||||||
import gevent
|
|
||||||
import gevent.monkey
|
|
||||||
import gevent.wsgi
|
|
||||||
import psycogreen.gevent
|
|
||||||
gevent.monkey.patch_all()
|
|
||||||
psycogreen.gevent.patch_psycopg()
|
|
||||||
import threading
|
|
||||||
import openerp
|
|
||||||
import openerp.cli.server
|
|
||||||
import openerp.service.wsgi_server
|
|
||||||
import openerp.tools.config
|
|
||||||
config = openerp.tools.config
|
|
||||||
|
|
||||||
os.environ["TZ"] = "UTC"
|
|
||||||
common.set_addons(args)
|
|
||||||
|
|
||||||
openerp.multi_process = True
|
|
||||||
common.setproctitle('openerp-web')
|
|
||||||
|
|
||||||
openerp.cli.server.check_root_user()
|
|
||||||
openerp.netsvc.init_logger()
|
|
||||||
#openerp.cli.server.report_configuration()
|
|
||||||
|
|
||||||
target = openerp.service.wsgi_server.serve
|
|
||||||
if not args.gevent:
|
|
||||||
openerp.evented = False
|
|
||||||
openerp.cli.server.setup_signal_handlers(openerp.cli.server.signal_handler)
|
|
||||||
# TODO openerp.multi_process with a multi-threaded process probably
|
|
||||||
# doesn't work very well (e.g. waiting for all threads to complete
|
|
||||||
# before killing the process is not implemented).
|
|
||||||
arg = (args.interface, int(args.port), args.threaded)
|
|
||||||
threading.Thread(target=target, args=arg).start()
|
|
||||||
openerp.cli.server.quit_on_signals()
|
|
||||||
else:
|
|
||||||
openerp.evented = True
|
|
||||||
|
|
||||||
app = openerp.service.wsgi_server.application
|
|
||||||
server = gevent.wsgi.WSGIServer((args.interface, int(args.port)), app)
|
|
||||||
setup_signal_handlers(mk_signal_handler(server))
|
|
||||||
try:
|
|
||||||
server.serve_forever()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
try:
|
|
||||||
server.stop()
|
|
||||||
gevent.shutdown()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
sys.stderr.write("Forced shutdown.\n")
|
|
||||||
gevent.shutdown()
|
|
||||||
|
|
||||||
def add_parser(subparsers):
|
|
||||||
parser = subparsers.add_parser('web',
|
|
||||||
description='Run a normal OpenERP HTTP process. By default a '
|
|
||||||
'singly-threaded Werkzeug server is used.')
|
|
||||||
common.add_addons_argument(parser)
|
|
||||||
parser.add_argument('--interface', default='0.0.0.0',
|
|
||||||
help='HTTP interface to listen on (default is %(default)s)')
|
|
||||||
parser.add_argument('--port', metavar='INT', default=8069,
|
|
||||||
help='HTTP port to listen on (default is %(default)s)')
|
|
||||||
parser.add_argument('--threaded', action='store_true',
|
|
||||||
help='Use a multithreaded Werkzeug server (incompatible with --gevent)')
|
|
||||||
parser.add_argument('--gevent', action='store_true',
|
|
||||||
help="Use gevent's WSGI server (incompatible with --threaded)")
|
|
||||||
|
|
||||||
parser.set_defaults(run=run)
|
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 201 KiB |
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 139 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |