[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
This commit is contained in:
Antony Lesuisse 2014-05-29 18:33:04 +02:00
parent 045ecc603e
commit d4624fa826
93 changed files with 188 additions and 6857 deletions

View File

@ -1,73 +1,48 @@
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,
there are several options:
If you are a developer type the following command at your terminal:
* download http://nightly.openerp.com/move-branch.zip and run it with
`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)
* Extract the branch contents as patches and use `git apply` or `git am` to
rebuild a branch from them
* Replay the branch by hand
wget -O- https://raw.githubusercontent.com/odoo/odoo/master/odoo.py | python
Then follow the tutorial here:
https://doc.openerp.com/trunk/server/howto/howto_website/
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:
$ sudo apt-get update
$ 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".
* Source tarballs http://nightly.openerp.com/
* Windows installer http://nightly.openerp.com/
* RPM package http://nightly.openerp.com/

View File

@ -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

View File

@ -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``.

View File

@ -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

View File

@ -30,16 +30,6 @@ OpenERP Server
form-view-guidelines
ir_actions
OpenERP Command
'''''''''''''''
.. toctree::
:maxdepth: 1
openerp-command.rst
commands.rst
adding-command.rst
OpenERP Server API
''''''''''''''''''

View File

@ -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.

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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!

View File

@ -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

View File

@ -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:

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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:

View File

@ -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:

View File

@ -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.

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -1 +0,0 @@
Those scripts are provide as example of customization of migration scripts

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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

158
odoo.py Executable file
View File

@ -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
View File

@ -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()

View File

@ -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

View File

@ -1,3 +0,0 @@
# -*- coding: utf-8 -*-
# Nothing here, the module provides only data.
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -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:

View File

@ -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

View File

@ -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, {})

View File

@ -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)

View File

@ -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)

View File

@ -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))

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
{% for module in modules if module -%}
import {{ module }}
{% endfor %}

View File

@ -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': [
],
}

View File

@ -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!"

View File

@ -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 %}

View File

@ -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(),
}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 201 KiB

View File

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 139 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB