diff --git a/INSTALL b/INSTALL
index c52e5fbbf4..dea2f76ec6 100644
--- a/INSTALL
+++ b/INSTALL
@@ -6,6 +6,7 @@ Prerequisites
* Python 2.6 or later (http://www.python.org/)
* trytond (http://www.tryton.org/)
+ * python-sql (http://code.google.com/p/python-sql/)
* trytond_company (http://www.tryton.org/)
* trytond_party (http://www.tryton.org/)
* trytond_stock (http://www.tryton.org/)
diff --git a/invoice.py b/invoice.py
index 1f3294fe5e..f387e7b8e8 100644
--- a/invoice.py
+++ b/invoice.py
@@ -1,11 +1,12 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
from functools import wraps
+from sql import Table
from trytond.model import ModelView, Workflow, fields
from trytond.pool import Pool, PoolMeta
from trytond.transaction import Transaction
-from trytond.backend import TableHandler
+from trytond import backend
__all__ = ['Invoice', 'InvoiceLine']
__metaclass__ = PoolMeta
@@ -66,11 +67,14 @@ class Invoice:
@classmethod
def delete(cls, invoices):
+ pool = Pool()
+ Purchase_Invoice = pool.get('purchase.purchase-account.invoice')
+ purchase_invoice = Purchase_Invoice.__table__()
cursor = Transaction().cursor
if invoices:
- cursor.execute('SELECT id FROM purchase_invoices_rel '
- 'WHERE invoice IN (' + ','.join(('%s',) * len(invoices)) + ')',
- [i.id for i in invoices])
+ cursor.execute(*purchase_invoice.select(purchase_invoice.id,
+ where=purchase_invoice.invoice.in_(
+ [i.id for i in invoices])))
if cursor.fetchone():
cls.raise_user_error('delete_purchase_invoice')
super(Invoice, cls).delete(invoices)
@@ -118,22 +122,25 @@ class InvoiceLine:
@classmethod
def __register__(cls, module_name):
+ TableHandler = backend.get('TableHandler')
cursor = Transaction().cursor
+ sql_table = cls.__table__()
super(InvoiceLine, cls).__register__(module_name)
# Migration from 2.6: remove purchase_lines
- rel_table = 'purchase_line_invoice_lines_rel'
- if TableHandler.table_exist(cursor, rel_table):
- cursor.execute('SELECT purchase_line, invoice_line '
- 'FROM "' + rel_table + '"')
+ rel_table_name = 'purchase_line_invoice_lines_rel'
+ if TableHandler.table_exist(cursor, rel_table_name):
+ rel_table = Table(rel_table_name)
+ cursor.execute(*rel_table.select(
+ rel_table.purchase_line, rel_table.invoice_line))
for purchase_line, invoice_line in cursor.fetchall():
- cursor.execute('UPDATE "' + cls._table + '" '
- 'SET origin = %s '
- 'WHERE id = %s',
- ('purchase.line,%s' % purchase_line, invoice_line))
+ cursor.execute(*sql_table.update(
+ columns=[sql_table.origin],
+ values=['purchase.line,%s' % purchase_line],
+ where=sql_table.id == invoice_line))
TableHandler.drop_table(cursor,
- 'purchase.line-account.invoice.line', rel_table)
+ 'purchase.line-account.invoice.line', rel_table_name)
@classmethod
def _get_origin(cls):
diff --git a/purchase.py b/purchase.py
index 134374335a..0d639cc637 100644
--- a/purchase.py
+++ b/purchase.py
@@ -3,11 +3,16 @@
import datetime
from itertools import chain
from decimal import Decimal
+from sql import Table, Literal
+from sql.functions import Overlay, Position
+from sql.aggregate import Count
+from sql.operators import Concat
+
from trytond.model import Workflow, ModelView, ModelSQL, fields
from trytond.modules.company import CompanyReport
from trytond.wizard import Wizard, StateAction, StateView, StateTransition, \
Button
-from trytond.backend import TableHandler
+from trytond import backend
from trytond.pyson import Eval, Bool, If, PYSONEncoder, Id
from trytond.transaction import Transaction
from trytond.pool import Pool, PoolMeta
@@ -194,18 +199,34 @@ class Purchase(Workflow, ModelSQL, ModelView):
@classmethod
def __register__(cls, module_name):
+ TableHandler = backend.get('TableHandler')
cursor = Transaction().cursor
+ model_data = Table('ir_model_data')
+ model_field = Table('ir_model_field')
+ sql_table = cls.__table__()
+
# Migration from 1.2: packing renamed into shipment
- cursor.execute("UPDATE ir_model_data "
- "SET fs_id = REPLACE(fs_id, 'packing', 'shipment') "
- "WHERE fs_id like '%%packing%%' AND module = %s",
- (module_name,))
- cursor.execute("UPDATE ir_model_field "
- "SET relation = REPLACE(relation, 'packing', 'shipment'), "
- "name = REPLACE(name, 'packing', 'shipment') "
- "WHERE (relation like '%%packing%%' "
- "OR name like '%%packing%%') AND module = %s",
- (module_name,))
+ cursor.execute(*model_data.update(
+ columns=[model_data.fs_id],
+ values=[Overlay(model_data.fs_id, 'shipment',
+ Position('packing', model_data.fs_id),
+ len('packing'))],
+ where=model_data.fs_id.like('%packing%')
+ & (model_data.module == module_name)))
+ cursor.execute(*model_field.update(
+ columns=[model_field.relation],
+ values=[Overlay(model_field.relation, 'shipment',
+ Position('packing', model_field.relation),
+ len('packing'))],
+ where=model_field.relation.like('%packing%')
+ & (model_field.module == module_name)))
+ cursor.execute(*model_field.update(
+ columns=[model_field.name],
+ values=[Overlay(model_field.name, 'shipment',
+ Position('packing', model_field.name),
+ len('packing'))],
+ where=model_field.name.like('%packing%')
+ & (model_field.module == module_name)))
table = TableHandler(cursor, cls, module_name)
table.column_rename('packing_state', 'shipment_state')
@@ -213,9 +234,10 @@ class Purchase(Workflow, ModelSQL, ModelView):
# Migration from 1.2: rename packing to shipment in
# invoice_method values
- cursor.execute("UPDATE " + cls._table + " "
- "SET invoice_method = 'shipment' "
- "WHERE invoice_method = 'packing'")
+ cursor.execute(*sql_table.update(
+ columns=[sql_table.invoice_method],
+ values=['shipment'],
+ where=sql_table.invoice_method == 'packing'))
table = TableHandler(cursor, cls, module_name)
# Migration from 2.2: warehouse is no more required
@@ -286,6 +308,7 @@ class Purchase(Workflow, ModelSQL, ModelView):
PaymentTerm = pool.get('account.invoice.payment_term')
Currency = pool.get('currency.currency')
cursor = Transaction().cursor
+ table = self.__table__()
changes = {
'invoice_address': None,
'payment_term': None,
@@ -299,13 +322,13 @@ class Purchase(Workflow, ModelSQL, ModelView):
if self.party.supplier_payment_term:
payment_term = self.party.supplier_payment_term
- subquery = cursor.limit_clause('SELECT currency '
- 'FROM "' + self._table + '" '
- 'WHERE party = %s '
- 'ORDER BY id DESC', 10)
- cursor.execute('SELECT currency FROM (' + subquery + ') AS p '
- 'GROUP BY currency '
- 'ORDER BY COUNT(1) DESC', (self.party.id,))
+ subquery = table.select(table.currency,
+ where=table.party == self.party.id,
+ order_by=table.id,
+ limit=10)
+ cursor.execute(*subquery.select(subquery.currency,
+ group_by=subquery.currency,
+ order_by=Count(Literal(1)).desc))
row = cursor.fetchone()
if row:
currency_id, = row
@@ -799,9 +822,7 @@ class PurchaseLine(ModelSQL, ModelView):
_rec_name = 'description'
purchase = fields.Many2One('purchase.purchase', 'Purchase',
ondelete='CASCADE', select=True, required=True)
- sequence = fields.Integer('Sequence',
- order_field='(%(table)s.sequence IS NULL) %(order)s, '
- '%(table)s.sequence %(order)s')
+ sequence = fields.Integer('Sequence')
type = fields.Selection([
('line', 'Line'),
('subtotal', 'Subtotal'),
@@ -920,19 +941,27 @@ class PurchaseLine(ModelSQL, ModelView):
@classmethod
def __register__(cls, module_name):
- super(PurchaseLine, cls).__register__(module_name)
+ TableHandler = backend.get('TableHandler')
cursor = Transaction().cursor
+ sql_table = cls.__table__()
+ super(PurchaseLine, cls).__register__(module_name)
table = TableHandler(cursor, cls, module_name)
# Migration from 1.0 comment change into note
if table.column_exist('comment'):
- cursor.execute('UPDATE "' + cls._table + '" '
- 'SET note = comment')
+ cursor.execute(*sql_table.update(
+ columns=[sql_table.note],
+ values=[sql_table.comment]))
table.drop_column('comment', exception=True)
# Migration from 2.4: drop required on sequence
table.not_null_action('sequence', action='remove')
+ @staticmethod
+ def order_sequence(tables):
+ table, _ = tables[None]
+ return [table.sequence == None, table.sequence]
+
@staticmethod
def default_type():
return 'line'
@@ -1427,9 +1456,7 @@ class ProductSupplier(ModelSQL, ModelView):
ondelete='CASCADE', select=True, on_change=['party'])
name = fields.Char('Name', size=None, translate=True, select=True)
code = fields.Char('Code', size=None, select=True)
- sequence = fields.Integer('Sequence',
- order_field='(%(table)s.sequence IS NULL) %(order)s, '
- '%(table)s.sequence %(order)s')
+ sequence = fields.Integer('Sequence')
prices = fields.One2Many('purchase.product_supplier.price',
'product_supplier', 'Prices')
company = fields.Many2One('company.company', 'Company', required=True,
@@ -1449,8 +1476,10 @@ class ProductSupplier(ModelSQL, ModelView):
@classmethod
def __register__(cls, module_name):
+ TableHandler = backend.get('TableHandler')
cursor = Transaction().cursor
table = TableHandler(cursor, cls, module_name)
+ sql_table = cls.__table__()
# Migration from 2.2 new field currency
created_currency = table.column_exist('currency')
@@ -1460,21 +1489,21 @@ class ProductSupplier(ModelSQL, ModelView):
# Migration from 2.2 fill currency
if not created_currency:
Company = Pool().get('company.company')
+ company = Company.__table__()
limit = cursor.IN_MAX
- cursor.execute('SELECT count(id) FROM "' + cls._table + '"')
+ cursor.execute(*sql_table.select(Count(sql_table.id)))
product_supplier_count, = cursor.fetchone()
for offset in range(0, product_supplier_count, limit):
- cursor.execute(cursor.limit_clause(
- 'SELECT p.id, c.currency '
- 'FROM "' + cls._table + '" AS p '
- 'INNER JOIN "' + Company._table + '" AS c '
- 'ON p.company = c.id '
- 'ORDER BY p.id',
- limit, offset))
+ cursor.execute(*sql_table.join(company,
+ condition=sql_table.company == company.id
+ ).select(sql_table.id, company.currency,
+ order_by=sql_table.id,
+ limit=limit, offset=offset))
for product_supplier_id, currency_id in cursor.fetchall():
- cursor.execute('UPDATE "' + cls._table + '" '
- 'SET currency = %s '
- 'WHERE id = %s', (currency_id, product_supplier_id))
+ cursor.execute(*sql_table.update(
+ columns=[sql_table.currency],
+ values=[currency_id],
+ where=sql_table.id == product_supplier_id))
# Migration from 2.4: drop required on sequence
table.not_null_action('sequence', action='remove')
@@ -1482,6 +1511,11 @@ class ProductSupplier(ModelSQL, ModelView):
# Migration from 2.6: drop required on delivery_time
table.not_null_action('delivery_time', action='remove')
+ @staticmethod
+ def order_sequence(tables):
+ table, _ = tables[None]
+ return [table.sequence == None, table.sequence]
+
@staticmethod
def default_company():
return Transaction().context.get('company')
@@ -1499,10 +1533,11 @@ class ProductSupplier(ModelSQL, ModelView):
'currency': self.default_currency(),
}
if self.party:
- cursor.execute('SELECT currency FROM "' + self._table + '" '
- 'WHERE party = %s '
- 'GROUP BY currency '
- 'ORDER BY COUNT(1) DESC', (self.party.id,))
+ table = self.__table__()
+ cursor.execute(*table.select(table.currency,
+ where=table.party == self.party.id,
+ group_by=table.currency,
+ order_by=Count(Literal(1)).desc))
row = cursor.fetchone()
if row:
changes['currency'], = row
@@ -1704,7 +1739,9 @@ class Move(ModelSQL, ModelView):
@classmethod
def __register__(cls, module_name):
+ TableHandler = backend.get('TableHandler')
cursor = Transaction().cursor
+ sql_table = cls.__table__()
super(Move, cls).__register__(module_name)
@@ -1712,9 +1749,10 @@ class Move(ModelSQL, ModelView):
# Migration from 2.6: remove purchase_line
if table.column_exist('purchase_line'):
- cursor.execute('UPDATE "' + cls._table + '" '
- 'SET origin = \'purchase.line,\' || purchase_line '
- 'WHERE purchase_line IS NOT NULL')
+ cursor.execute(*sql_table.update(
+ columns=[sql_table.origin],
+ values=[Concat('purchase.line,', sql_table.purchase_line)],
+ where=sql_table.purchase_line != None))
table.drop_column('purchase_line')
@classmethod
@@ -1822,9 +1860,12 @@ class OpenSupplier(Wizard):
pool = Pool()
ModelData = pool.get('ir.model.data')
Wizard = pool.get('ir.action.wizard')
+ Purchase = pool.get('purchase.purchase')
cursor = Transaction().cursor
+ purchase = Purchase.__table__()
- cursor.execute("SELECT DISTINCT(party) FROM purchase_purchase")
+ cursor.execute(*purchase.select(purchase.party,
+ group_by=purchase.party))
supplier_ids = [line[0] for line in cursor.fetchall()]
action['pyson_domain'] = PYSONEncoder().encode(
[('id', 'in', supplier_ids)])
@@ -1853,11 +1894,15 @@ class HandleShipmentExceptionAsk(ModelView):
@classmethod
def __register__(cls, module_name):
cursor = Transaction().cursor
+ model = Table('ir_model')
# Migration from 1.2: packing renamed into shipment
- cursor.execute("UPDATE ir_model "
- "SET model = REPLACE(model, 'packing', 'shipment') "
- "WHERE model like '%%packing%%' AND module = %s",
- (module_name,))
+ cursor.execute(*model.update(
+ columns=[model.model],
+ values=[Overlay(model.model, 'shipment',
+ Position('packing', model.model),
+ len('packing'))],
+ where=model.model.like('%packing%')
+ & (model.module == module_name)))
super(HandleShipmentExceptionAsk, cls).__register__(module_name)
diff --git a/setup.py b/setup.py
index b231cb4089..facd90dd7e 100644
--- a/setup.py
+++ b/setup.py
@@ -21,7 +21,7 @@ major_version, minor_version, _ = info.get('version', '0.0.1').split('.', 2)
major_version = int(major_version)
minor_version = int(minor_version)
-requires = []
+requires = ['python-sql']
for dep in info.get('depends', []):
if not re.match(r'(ir|res|webdav)(\W|$)', dep):
requires.append('trytond_%s >= %s.%s, < %s.%s' %
diff --git a/tests/scenario_purchase.rst b/tests/scenario_purchase.rst
index a40ac6de9c..3f93c9dcbc 100644
--- a/tests/scenario_purchase.rst
+++ b/tests/scenario_purchase.rst
@@ -112,7 +112,7 @@ Create chart of accounts::
>>> AccountTemplate = Model.get('account.account.template')
>>> Account = Model.get('account.account')
- >>> account_template, = AccountTemplate.find([('parent', '=', False)])
+ >>> account_template, = AccountTemplate.find([('parent', '=', None)])
>>> create_chart = Wizard('account.create_chart')
>>> create_chart.execute('account')
>>> create_chart.form.account_template = account_template
diff --git a/view/purchase_tree.xml b/view/purchase_tree.xml
index 7778ed485e..4f16d6c6cb 100644
--- a/view/purchase_tree.xml
+++ b/view/purchase_tree.xml
@@ -15,5 +15,4 @@ this repository contains the full copyright notices and license terms. -->
-