Skip to content

Commit

Permalink
Export data and format update
Browse files Browse the repository at this point in the history
  • Loading branch information
kmagusiak committed Jan 2, 2024
1 parent 66d47c1 commit 454c6fa
Show file tree
Hide file tree
Showing 13 changed files with 569 additions and 377 deletions.
128 changes: 70 additions & 58 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ A simple library to use Odoo RPC.

## Usage

import odoo_connect
odoo = env = odoo_connect.connect(url='http://localhost', username='admin', password='admin')
so = env['sale.order']
so.search_read([('create_uid', '=', 1)], [])
```python
import odoo_connect
odoo = env = odoo_connect.connect(url='http://localhost:8069', username='admin', password='admin')
so = env['sale.order']
so.search_read([('create_uid', '=', 1)], [])
```

## Rationale

Expand All @@ -25,79 +27,89 @@ and call methods, so the maintenance should be minimal.
Note that each RPC call is executed in a transaction.
So the following code on the server, will add one to every line ordered
quantity or fail and do nothing.
However, ORM client libraries will perform multiple steps, on a failure,
However, RPC client libraries will perform multiple steps, on a failure,
already executed code was committed. You can end with race conditions
where some other code sets product_uom_qty to 0 before you increment it.

lines = env['sale.order.line'].search([
('order_id.name', '=', 'S00001')
])
for line in lines:
if line.product_uom_qty > 1:
line.product_uom_qty += 1

A better way of doing something like this is to implement a function on Odoo
side and call it. `lines.increment_qty([('product_uom_qty', '>', 1)])`.
A better way of doing this is to implement a function on Odoo side and call it.

```python
lines = env['sale.order.line'].search([
('order_id.name', '=', 'S00001')
])
# this is fine on the server, but not in RPC (multiple transactions)
for line in lines:
if line.product_uom_qty > 1:
line.product_uom_qty += 1
# single transaction
lines.increment_qty([('product_uom_qty', '>', 1)])
```

## Export and import data

A separate package provides utilities to more easily extract data from Odoo.
It also contains utility to get binary data (attachments) and reports.
It also contains utility to get binary data (attachments) and reports;
however this requires administrative permissions.

Since Odoo doesn't accept all kind of values, the *format* package will help
with converting between user-expected values and values returned by Odoo.
Since Odoo doesn't accept all kind of values, the `format` package will help
with converting between python values and values returned by Odoo.

The following function will return a table-like (list of lists) structure
The provided function will return a table-like (list of lists) structure
with the requested data.
You can also pass filter names or export names instead of, respectively,
domains and fields. Note that this doesn't support groupping.

# Read data as usual
env['sale.order'].search_read_dict([('state', '=', 'sale')], ['name', 'partner_id.name'])
env['sale.order'].read_group([], ['amount_untaxed'], ['partner_id', 'create_date:month'])

# Export data
import odoo_connect.data as odoo_data
so = env['sale.order']
data = odoo_data.export_data(so, [('state', '=', 'sale')], ['name', 'partner_id.name'])
odoo_data.add_url(so, data)

# Import data using Odoo's load() function
odoo_data.load_data(so, data)

# Import data using writes and creates (or another custom method)
for batch in odoo_data.make_batches(data):
# add ids by querying the model using the 'name' field
odoo_data.add_fields(so, batch, 'name', ['id'])
# if you just plan to create(), you can skip adding ids
odoo_data.load_data(partner, batch, method='write')

## Data types

A small module provides functions to translate from JSON values to binary
or date values.
You can also pass `ir.filters` names or `ir.exports` names instead of,
respectively, domains and fields. Note that this doesn't support groupping.

```python
import odoo_connect.data as odoo_data
so = env['sale.order']

# Read data as usual
data = so.search_read_dict([('state', '=', 'sale')], ['name', 'partner_id.name'])
so.read_group([], ['amount_untaxed'], ['partner_id', 'create_date:month'])
odoo_data.add_url(so, data)

# Exporting flattened data
all_data = odoo_data.export_data(so, [('state', '=', 'sale')], ['name', 'partner_id.name'])
with io.StringIO(newline='') as f:
w = csv.writer(f, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
w.writerows(all_data.to_csv())
all_data.to_pandas() # as a data frame
all_data.to_sql(con, 'table_name') # create a table

# Import data using Odoo's load() function
odoo_data.load_data(so, data)

# Import data using writes and creates (or another custom method)
for batch in odoo_data.make_batches(data):
# add ids by querying the model using the 'name' field
# if you remove 'id' from the data, only create() is called
odoo_data.add_fields(so, batch, 'name', ['id'])
odoo_data.load_data(so, batch, method='write')
```

## Explore

Provides a simple abstraction for querying data with a local cache.
It may be easier than executing and parsing a `read()`.
Also, auto-completion for fields is provided in jupyter.

from odoo_connect.explore import explore
sale_order = explore(env['sale.order'])
sale_order = sale_order.search([], limit=1)
sale_order.read()

```python
from odoo_connect.explore import explore
sale_order = explore(env['sale.order'])
sale_order = sale_order.search([], limit=1)
sale_order.read()
```

## Development

You can use a vscode container and open this repository inside it.
Alternatively, clone and setup the repository manually.

git clone $url
cd odoo-connect
# Install dev libraries
pip install -r requirements.txt
./pre-commit install
# Run some tests
pytest
```bash
git clone $url
cd odoo-connect
# Install dev libraries
pip install -r requirements.txt
./pre-commit install
# Run some tests
pytest
```
2 changes: 1 addition & 1 deletion odoo_connect/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def connect(
if not username and urlx.username:
# read username and password from the url
username = urlx.username
password = urlx.password
password = urlx.password or password
if not password and username:
# copy username to password when not set
password = username
Expand Down
Loading

0 comments on commit 454c6fa

Please sign in to comment.