diff --git a/addons/account/account.py b/addons/account/account.py index ddb90cc13cc..29ea824f171 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -818,6 +818,7 @@ class account_move(osv.osv): ('journal_pur_voucher','Journal Purchase'), ('journal_voucher','Journal Voucher'), ],'Type', readonly=True, select=True, states={'draft':[('readonly',False)]}), + 'narration':fields.text('Narration', readonly=True, select=True, states={'draft':[('readonly',False)]}), 'company_id': fields.related('journal_id','company_id',type='many2one',relation='res.company',string='Company',store=True), } _defaults = { diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml index 2034583ed02..a8de84f2cdc 100644 --- a/addons/account/account_invoice_view.xml +++ b/addons/account/account_invoice_view.xml @@ -349,6 +349,18 @@ {'type':'out_invoice'} + + + Panding Invoice + account.invoice + form + tree,form,calendar,graph + + {'type':'out_invoice'} + [('state','=','draft')] + + + tree diff --git a/addons/account/account_view.xml b/addons/account/account_view.xml index 82f49d83cae..8be310245ac 100644 --- a/addons/account/account_view.xml +++ b/addons/account/account_view.xml @@ -917,7 +917,8 @@ - + + diff --git a/addons/account_analytic_analysis/i18n/nl.po b/addons/account_analytic_analysis/i18n/nl.po index bfff9f3ec71..c3f12aef08c 100644 --- a/addons/account_analytic_analysis/i18n/nl.po +++ b/addons/account_analytic_analysis/i18n/nl.po @@ -7,13 +7,13 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.0_rc3\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2009-08-28 16:01+0000\n" -"PO-Revision-Date: 2009-12-11 12:37+0000\n" -"Last-Translator: Pieter J. Kersten (EduSense BV) \n" +"PO-Revision-Date: 2010-02-01 15:46+0000\n" +"Last-Translator: Jan Verlaan (Veritos) \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-12-16 05:16+0000\n" +"X-Launchpad-Export-Date: 2010-02-02 04:48+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: account_analytic_analysis @@ -191,7 +191,7 @@ msgstr "Berekend met de formule: gefactureerd bedrag - totale kosten." #: model:ir.actions.act_window,name:account_analytic_analysis.action_account_analytic_managed #: model:ir.ui.menu,name:account_analytic_analysis.menu_analytic_account_managed msgid "My Accounts" -msgstr "Mijn rekeningen" +msgstr "Mijn kostenplaatsen" #. module: account_analytic_analysis #: model:ir.module.module,description:account_analytic_analysis.module_meta_information @@ -335,7 +335,7 @@ msgstr "Kostenplaats" #: model:ir.actions.act_window,name:account_analytic_analysis.action_account_analytic_managed_overpassed #: model:ir.ui.menu,name:account_analytic_analysis.menu_action_account_analytic_managed_overpassed msgid "Overpassed Accounts" -msgstr "Overbrugde Kostenplaatsen" +msgstr "Overgehevelde kostenplaatsen" #. module: account_analytic_analysis #: model:ir.actions.act_window,name:account_analytic_analysis.action_hr_tree_invoiced_all diff --git a/addons/document_caldav/__init__.py b/addons/account_anglo_saxon/__init__.py similarity index 89% rename from addons/document_caldav/__init__.py rename to addons/account_anglo_saxon/__init__.py index d764dfca72d..dd9a9262710 100644 --- a/addons/document_caldav/__init__.py +++ b/addons/account_anglo_saxon/__init__.py @@ -1,8 +1,7 @@ -# -*- coding: utf-8 -*- ############################################################################## # # OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 Tiny SPRL (). +# Copyright (C) 2004-2009 Tiny SPRL (). # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -19,5 +18,8 @@ # ############################################################################## -import document_caldav -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: +import product +import stock +import purchase +import invoice +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file diff --git a/addons/account_anglo_saxon/__terp__.py b/addons/account_anglo_saxon/__terp__.py new file mode 100644 index 00000000000..31818e213ef --- /dev/null +++ b/addons/account_anglo_saxon/__terp__.py @@ -0,0 +1,39 @@ +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). +# +# 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 . +# +############################################################################## +{ + "name" : "Stock Account", + "version" : "1.1", + "author" : "Tiny", + "website" : "http://tinyerp.com", + "description" : """This module will support the Anglo-Saxons accounting methodology by + changing the accounting logic with stock transactions. The difference between the Anglo-Saxon accounting countries + and the Rhine or also called Continental accounting countries is the moment of taking the Cost of Goods Sold versus Cost of Sales. + Anglo-Saxons accounting does take the cost when sales invoice is created, Continental accounting will take the cost at he moment the goods are shipped. + This module will add this functionality by using a interim account, to store the value of shipped goods and will contra book this interim account + when the invoice is created to transfer this amount to the debtor or creditor account.""", + "depends" : ["product", "account", "sale", "purchase"], + "category" : "Generic Modules/Inventory Control", + "init_xml" : [], + "demo_xml" : [], + "update_xml" : ["product_view.xml",], + "active": False, + "installable": True +} +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file diff --git a/addons/account_anglo_saxon/i18n/account_anglo_saxon.pot b/addons/account_anglo_saxon/i18n/account_anglo_saxon.pot new file mode 100644 index 00000000000..a2d316633a4 --- /dev/null +++ b/addons/account_anglo_saxon/i18n/account_anglo_saxon.pot @@ -0,0 +1,54 @@ +# Translation of OpenERP Server. +# This file contains the translation of the following modules: +# * account_anglo_saxon +# +msgid "" +msgstr "" +"Project-Id-Version: OpenERP Server 5.0.6\n" +"Report-Msgid-Bugs-To: support@openerp.com\n" +"POT-Creation-Date: 2009-11-24 12:50:50+0000\n" +"PO-Revision-Date: 2009-11-24 12:50:50+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: account_anglo_saxon +#: view:product.category:0 +msgid " Accounting Property" +msgstr "" + +#. module: account_anglo_saxon +#: model:ir.module.module,description:account_anglo_saxon.module_meta_information +msgid "This module will support the Anglo-Saxons accounting methodology by \n" +" changing the accounting logic with stock transactions. The difference between the Anglo-Saxon accounting countries \n" +" and the Rhine or also called Continental accounting countries is the moment of taking the Cost of Goods Sold versus Cost of Sales. \n" +" Anglo-Saxons accounting does take the cost when sales invoice is created, Continental accounting will take the cost at he moment the goods are shipped.\n" +" This module will add this functionality by using a interim account, to store the value of shipped goods and will contra book this interim account \n" +" when the invoice is created to transfer this amount to the debtor or creditor account." +msgstr "" + +#. module: account_anglo_saxon +#: model:ir.module.module,shortdesc:account_anglo_saxon.module_meta_information +msgid "Stock Account" +msgstr "" + +#. module: account_anglo_saxon +#: constraint:ir.ui.view:0 +msgid "Invalid XML for View Architecture!" +msgstr "" + +#. module: account_anglo_saxon +#: field:product.category,property_account_creditor_price_difference_categ:0 +#: field:product.template,property_account_creditor_price_difference:0 +msgid "Price Difference Account" +msgstr "" + +#. module: account_anglo_saxon +#: help:product.category,property_account_creditor_price_difference_categ:0 +#: help:product.template,property_account_creditor_price_difference:0 +msgid "This account will be used to value price difference between purchase price and cost price." +msgstr "" + diff --git a/addons/account_anglo_saxon/i18n/fr_BE.po b/addons/account_anglo_saxon/i18n/fr_BE.po new file mode 100644 index 00000000000..a2d316633a4 --- /dev/null +++ b/addons/account_anglo_saxon/i18n/fr_BE.po @@ -0,0 +1,54 @@ +# Translation of OpenERP Server. +# This file contains the translation of the following modules: +# * account_anglo_saxon +# +msgid "" +msgstr "" +"Project-Id-Version: OpenERP Server 5.0.6\n" +"Report-Msgid-Bugs-To: support@openerp.com\n" +"POT-Creation-Date: 2009-11-24 12:50:50+0000\n" +"PO-Revision-Date: 2009-11-24 12:50:50+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: account_anglo_saxon +#: view:product.category:0 +msgid " Accounting Property" +msgstr "" + +#. module: account_anglo_saxon +#: model:ir.module.module,description:account_anglo_saxon.module_meta_information +msgid "This module will support the Anglo-Saxons accounting methodology by \n" +" changing the accounting logic with stock transactions. The difference between the Anglo-Saxon accounting countries \n" +" and the Rhine or also called Continental accounting countries is the moment of taking the Cost of Goods Sold versus Cost of Sales. \n" +" Anglo-Saxons accounting does take the cost when sales invoice is created, Continental accounting will take the cost at he moment the goods are shipped.\n" +" This module will add this functionality by using a interim account, to store the value of shipped goods and will contra book this interim account \n" +" when the invoice is created to transfer this amount to the debtor or creditor account." +msgstr "" + +#. module: account_anglo_saxon +#: model:ir.module.module,shortdesc:account_anglo_saxon.module_meta_information +msgid "Stock Account" +msgstr "" + +#. module: account_anglo_saxon +#: constraint:ir.ui.view:0 +msgid "Invalid XML for View Architecture!" +msgstr "" + +#. module: account_anglo_saxon +#: field:product.category,property_account_creditor_price_difference_categ:0 +#: field:product.template,property_account_creditor_price_difference:0 +msgid "Price Difference Account" +msgstr "" + +#. module: account_anglo_saxon +#: help:product.category,property_account_creditor_price_difference_categ:0 +#: help:product.template,property_account_creditor_price_difference:0 +msgid "This account will be used to value price difference between purchase price and cost price." +msgstr "" + diff --git a/addons/account_anglo_saxon/i18n/pl.po b/addons/account_anglo_saxon/i18n/pl.po new file mode 100644 index 00000000000..f23f6949fee --- /dev/null +++ b/addons/account_anglo_saxon/i18n/pl.po @@ -0,0 +1,66 @@ +# Polish translation for openobject-addons +# Copyright (c) 2010 Rosetta Contributors and Canonical Ltd 2010 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2010. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2009-11-24 12:50+0000\n" +"PO-Revision-Date: 2010-01-06 01:42+0000\n" +"Last-Translator: Fabien (Open ERP) \n" +"Language-Team: Polish \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2010-01-22 04:42+0000\n" +"X-Generator: Launchpad (build Unknown)\n" + +#. module: account_anglo_saxon +#: view:product.category:0 +msgid " Accounting Property" +msgstr "" + +#. module: account_anglo_saxon +#: model:ir.module.module,description:account_anglo_saxon.module_meta_information +msgid "" +"This module will support the Anglo-Saxons accounting methodology by \n" +" changing the accounting logic with stock transactions. The difference " +"between the Anglo-Saxon accounting countries \n" +" and the Rhine or also called Continental accounting countries is the " +"moment of taking the Cost of Goods Sold versus Cost of Sales. \n" +" Anglo-Saxons accounting does take the cost when sales invoice is " +"created, Continental accounting will take the cost at he moment the goods " +"are shipped.\n" +" This module will add this functionality by using a interim account, to " +"store the value of shipped goods and will contra book this interim account \n" +" when the invoice is created to transfer this amount to the debtor or " +"creditor account." +msgstr "" + +#. module: account_anglo_saxon +#: model:ir.module.module,shortdesc:account_anglo_saxon.module_meta_information +msgid "Stock Account" +msgstr "" + +#. module: account_anglo_saxon +#: constraint:ir.ui.view:0 +msgid "Invalid XML for View Architecture!" +msgstr "XML niewłaściwy dla tej architektury wyświetlania!" + +#. module: account_anglo_saxon +#: field:product.category,property_account_creditor_price_difference_categ:0 +#: field:product.template,property_account_creditor_price_difference:0 +msgid "Price Difference Account" +msgstr "Konto różnic cenowych" + +#. module: account_anglo_saxon +#: help:product.category,property_account_creditor_price_difference_categ:0 +#: help:product.template,property_account_creditor_price_difference:0 +msgid "" +"This account will be used to value price difference between purchase price " +"and cost price." +msgstr "" +"To konto będzie stosowane do rejestrowania różnic cenowych między ceną " +"zakupu a kosztem." diff --git a/addons/account_anglo_saxon/invoice.py b/addons/account_anglo_saxon/invoice.py new file mode 100644 index 00000000000..3904cd6b01e --- /dev/null +++ b/addons/account_anglo_saxon/invoice.py @@ -0,0 +1,158 @@ +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). +# +# 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 . +# +############################################################################## + +from osv import fields, osv + +class account_invoice_line(osv.osv): + _inherit = "account.invoice.line" + + def move_line_get(self, cr, uid, invoice_id, context=None): + res = super(account_invoice_line,self).move_line_get(cr, uid, invoice_id, context) + inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id) + if inv.type in ('out_invoice','out_refund'): + for i_line in inv.invoice_line: + if i_line.product_id: + if inv.type == 'out_invoice': + dacc = i_line.product_id.product_tmpl_id.property_stock_account_output and i_line.product_id.product_tmpl_id.property_stock_account_output.id + if not dacc: + dacc = i_line.product_id.categ_id.property_stock_account_output_categ and i_line.product_id.categ_id.property_stock_account_output_categ.id + else: + dacc = i_line.product_id.product_tmpl_id.property_stock_account_input and i_line.product_id.product_tmpl_id.property_stock_account_input.id + if not dacc: + dacc = i_line.product_id.categ_id.property_stock_account_input_categ and i_line.product_id.categ_id.property_stock_account_input_categ.id + + cacc = i_line.product_id.product_tmpl_id.property_account_expense and i_line.product_id.product_tmpl_id.property_account_expense.id + if not cacc: + cacc = i_line.product_id.categ_id.property_account_expense_categ and i_line.product_id.categ_id.property_account_expense_categ.id + if dacc and cacc: + res.append({ + 'type':'src', + 'name': i_line.name[:64], + 'price_unit':i_line.product_id.product_tmpl_id.standard_price, + 'quantity':i_line.quantity, + 'price':i_line.product_id.product_tmpl_id.standard_price * i_line.quantity, + 'account_id':dacc, + 'product_id':i_line.product_id.id, + 'uos_id':i_line.uos_id.id, + 'account_analytic_id':i_line.account_analytic_id.id, + 'taxes':i_line.invoice_line_tax_id, + }) + + res.append({ + 'type':'src', + 'name': i_line.name[:64], + 'price_unit':i_line.product_id.product_tmpl_id.standard_price, + 'quantity':i_line.quantity, + 'price': -1 * i_line.product_id.product_tmpl_id.standard_price * i_line.quantity, + 'account_id':cacc, + 'product_id':i_line.product_id.id, + 'uos_id':i_line.uos_id.id, + 'account_analytic_id':i_line.account_analytic_id.id, + 'taxes':i_line.invoice_line_tax_id, + }) + elif inv.type in ('in_invoice','in_refund'): + for i_line in inv.invoice_line: + if i_line.product_id: + if i_line.product_id.product_tmpl_id.type != 'service': + acc = i_line.product_id.product_tmpl_id.property_account_creditor_price_difference and i_line.product_id.product_tmpl_id.property_account_creditor_price_difference.id + if not acc: + acc = i_line.product_id.categ_id.property_account_creditor_price_difference_categ and i_line.product_id.categ_id.property_account_creditor_price_difference_categ.id + a = None + if inv.type == 'in_invoice': + oa = i_line.product_id.product_tmpl_id.property_stock_account_input and i_line.product_id.product_tmpl_id.property_stock_account_input.id + if not oa: + oa = i_line.product_id.categ_id.property_stock_account_input_categ and i_line.product_id.categ_id.property_stock_account_input_categ.id + else: + oa = i_line.product_id.product_tmpl_id.property_stock_account_output and i_line.product_id.product_tmpl_id.property_stock_account_output.id + if not oa: + oa = i_line.product_id.categ_id.property_stock_account_output_categ and i_line.product_id.categ_id.property_stock_account_output_categ.id + if oa: + fpos = i_line.invoice_id.fiscal_position or False + a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, oa) + diff_res = [] + for line in res: + if a == line['account_id'] and i_line.product_id.id == line['product_id']: + uom = i_line.product_id.uos_id or i_line.product_id.uom_id + standard_price = self.pool.get('product.uom')._compute_price(cr, uid, uom.id, i_line.product_id.product_tmpl_id.standard_price, i_line.uos_id.id) + if standard_price != i_line.price_unit and line['price'] == i_line.price_unit and acc: + price_diff = i_line.price_unit - standard_price + line.update({'price':standard_price * line['quantity']}) + diff_res.append({ + 'type':'src', + 'name': i_line.name[:64], + 'price_unit':price_diff, + 'quantity':line['quantity'], + 'price': price_diff * line['quantity'], + 'account_id':acc, + 'product_id':line['product_id'], + 'uos_id':line['uos_id'], + 'account_analytic_id':line['account_analytic_id'], + 'taxes':line.get('taxes',[]), + }) + res += diff_res + return res + + def product_id_change(self, cr, uid, ids, product, uom, qty=0, name='', type='out_invoice', partner_id=False, fposition_id=False, price_unit=False, address_invoice_id=False, context=None): + if not product: + return super(account_invoice_line, self).product_id_change(cr, uid, ids, product, uom, qty, name, type, partner_id, fposition_id, price_unit, address_invoice_id, context) + else: + res = super(account_invoice_line, self).product_id_change(cr, uid, ids, product, uom, qty, name, type, partner_id, fposition_id, price_unit, address_invoice_id, context) + + if type in ('in_invoice','in_refund'): + product_obj = self.pool.get('product.product').browse(cr, uid, product, context=context) + if type == 'in_invoice': + oa = product_obj.product_tmpl_id.property_stock_account_input and product_obj.product_tmpl_id.property_stock_account_input.id + if not oa: + oa = product_obj.categ_id.property_stock_account_input_categ and product_obj.categ_id.property_stock_account_input_categ.id + else: + oa = product_obj.product_tmpl_id.property_stock_account_output and product_obj.product_tmpl_id.property_stock_account_output.id + if not oa: + oa = product_obj.categ_id.property_stock_account_output_categ and product_obj.categ_id.property_stock_account_output_categ.id + if oa: + fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False + a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, oa) + res['value'].update({'account_id':a}) + return res + +account_invoice_line() + +class account_invoice(osv.osv): + _inherit = "account.invoice" + + def _refund_cleanup_lines(self, cr, uid, lines): + for line in lines: + inv_id = line['invoice_id'] + inv_obj = self.browse(cr,uid,inv_id[0]) + if inv_obj.type == 'in_invoice': + if line.get('product_id',False): + product_obj = self.pool.get('product.product').browse(cr,uid,line['product_id'][0]) + oa = product_obj.product_tmpl_id.property_stock_account_output and product_obj.product_tmpl_id.property_stock_account_output.id + if not oa: + oa = product_obj.categ_id.property_stock_account_output_categ and product_obj.categ_id.property_stock_account_output_categ.id + if oa: + fpos = inv_obj.fiscal_position or False + a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, oa) + account_data = self.pool.get('account.account').read(cr,uid,[a],['name'])[0] + line.update({'account_id': (account_data['id'],account_data['name'])}) + res = super(account_invoice,self)._refund_cleanup_lines(cr, uid, lines) + return res + +account_invoice() +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file diff --git a/addons/account_anglo_saxon/product.py b/addons/account_anglo_saxon/product.py new file mode 100644 index 00000000000..acd6577fbb7 --- /dev/null +++ b/addons/account_anglo_saxon/product.py @@ -0,0 +1,55 @@ +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). +# +# 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 . +# +############################################################################## + +from osv import fields, osv + +class product_category(osv.osv): + _inherit = "product.category" + _columns = { + 'property_account_creditor_price_difference_categ': fields.property( + 'account.account', + type='many2one', + relation='account.account', + string="Price Difference Account", + method=True, + view_load=True, + help="This account will be used to value price difference between purchase price and cost price."), + + } +product_category() + +class product_template(osv.osv): + _inherit = "product.template" + _columns = { + 'property_account_creditor_price_difference': fields.property( + 'account.account', + type='many2one', + relation='account.account', + string="Price Difference Account", + method=True, + view_load=True, + help="This account will be used to value price difference between purchase price and cost price."), + + } +product_template() + + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: + diff --git a/addons/account_anglo_saxon/product_view.xml b/addons/account_anglo_saxon/product_view.xml new file mode 100644 index 00000000000..78e51374128 --- /dev/null +++ b/addons/account_anglo_saxon/product_view.xml @@ -0,0 +1,53 @@ + + + + + product.normal.form.inherit.stock + product.product + form + + + + + + + + + + + + + product.template.product.form.inherit + product.template + form + + + + + + + + + + + + + product.category.property.form.inherit.stock + product.category + form + + +
+ + + + + + +
+
+
+ +
+
+ diff --git a/addons/account_anglo_saxon/purchase.py b/addons/account_anglo_saxon/purchase.py new file mode 100644 index 00000000000..98694a3d5dd --- /dev/null +++ b/addons/account_anglo_saxon/purchase.py @@ -0,0 +1,41 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). +# +# 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 . +# +############################################################################## + +from osv import fields, osv + +class purchase_order(osv.osv): + _name = "purchase.order" + _inherit = "purchase.order" + _description = "Purchase order" + + def inv_line_create(self, cr, uid, a, ol): + line = super(purchase_order, self).inv_line_create(cr, uid, a, ol) + if ol.product_id: + oa = ol.product_id.product_tmpl_id.property_stock_account_input and ol.product_id.product_tmpl_id.property_stock_account_input.id + if not oa: + oa = ol.product_id.categ_id.property_stock_account_input_categ and ol.product_id.categ_id.property_stock_account_input_categ.id + if oa: + fpos = ol.order_id.fiscal_position or False + a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, oa) + line[2].update({'account_id': a}) + return line +purchase_order() +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file diff --git a/addons/account_anglo_saxon/sale.py b/addons/account_anglo_saxon/sale.py new file mode 100644 index 00000000000..e2cfa7bbb3b --- /dev/null +++ b/addons/account_anglo_saxon/sale.py @@ -0,0 +1,43 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). +# +# 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 . +# +############################################################################## + +from osv import fields, osv + +#class sale_order_line(osv.osv): +# _name = 'sale.order.line' +# _description = 'Sale Order line' +# _inherit = 'sale.order.line' +# +# def invoice_line_create(self, cr, uid, ids, context={}): +# line_ids = super('sale_order_line',self).invoice_line_create(cr, uid, ids, context) +# invoice_line_obj = self.pool.get('account.invoice.line') +# for line in invoice_line_obj.browse(cr, uid, line_ids): +# if line.product_id: +# a = line.product_id.product_tmpl_id.property_stock_account_output and line.product_id.product_tmpl_id.property_stock_account_output.id +# if not a: +# a = line.product_id.categ_id.property_stock_account_output_categ and line.product_id.categ_id.property_stock_account_output_categ.id +# if a: +# a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a) +# invoice_line_obj.write(cr, uid, line.id, {'account_id':a}) +# +#sale_order_line() + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/account_anglo_saxon/stock.py b/addons/account_anglo_saxon/stock.py new file mode 100644 index 00000000000..ad091364fd8 --- /dev/null +++ b/addons/account_anglo_saxon/stock.py @@ -0,0 +1,51 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). +# +# 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 . +# +############################################################################## + +from osv import fields,osv + +#---------------------------------------------------------- +# Stock Picking +#---------------------------------------------------------- +class stock_picking(osv.osv): + _inherit = "stock.picking" + _description = "Packing List" + + def action_invoice_create(self, cr, uid, ids, journal_id=False, + group=False, type='out_invoice', context=None): + '''Return ids of created invoices for the pickings''' + res = super(stock_picking,self).action_invoice_create(cr, uid, ids, journal_id, group, type, context) + if type == 'in_refund': + for inv in self.pool.get('account.invoice').browse(cr, uid, res.values(), context=context): + for ol in inv.invoice_line: + if ol.product_id: + oa = ol.product_id.product_tmpl_id.property_stock_account_output and ol.product_id.product_tmpl_id.property_stock_account_output.id + if not oa: + oa = ol.product_id.categ_id.property_stock_account_output_categ and ol.product_id.categ_id.property_stock_account_output_categ.id + if oa: + fpos = ol.invoice_id.fiscal_position or False + a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, oa) + self.pool.get('account.invoice.line').write(cr, uid, [ol.id], {'account_id': a}) + return res + +stock_picking() + + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/account_followup/i18n/nl.po b/addons/account_followup/i18n/nl.po index 13782599c02..d756121ab3b 100644 --- a/addons/account_followup/i18n/nl.po +++ b/addons/account_followup/i18n/nl.po @@ -7,13 +7,13 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.0\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2009-08-28 16:01+0000\n" -"PO-Revision-Date: 2009-12-11 13:00+0000\n" -"Last-Translator: Pieter J. Kersten (EduSense BV) \n" +"PO-Revision-Date: 2010-02-01 15:48+0000\n" +"Last-Translator: Jan Verlaan (Veritos) \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-12-16 05:10+0000\n" +"X-Launchpad-Export-Date: 2010-02-02 04:48+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: account_followup @@ -55,7 +55,7 @@ msgstr "Bedrag in valuta" #. module: account_followup #: rml:account_followup.followup.print:0 msgid "Due" -msgstr "Tot" +msgstr "Bedrag" #. module: account_followup #: view:account.move.line:0 @@ -97,7 +97,7 @@ msgstr "Soort rekening" #. module: account_followup #: rml:account_followup.followup.print:0 msgid "Ref" -msgstr "Ref" +msgstr "Factuur" #. module: account_followup #: wizard_view:account_followup.followup.print.all,next:0 @@ -253,7 +253,7 @@ msgstr "" msgid "" "The Object name must start with x_ and not contain any special character !" msgstr "" -"De objectnaam moet beginnen met x_ en mag geen speciale karakters bevatten !" +"De objectnaam moet beginnen met x_ en mag geen speciale tekens bevatten !" #. module: account_followup #: wizard_button:account_followup.followup.print.all,summary,end:0 diff --git a/addons/account_voucher/account.py b/addons/account_voucher/account.py index ec886bef6c6..521767fa410 100644 --- a/addons/account_voucher/account.py +++ b/addons/account_voucher/account.py @@ -56,7 +56,6 @@ class account_account(osv.osv): return ids2 _columns = { - 'journal_id':fields.many2one('account.journal', 'Journal',domain=[('type','=','situation')]), 'open_bal' : fields.float('Opening Balance',digits=(16,2)), 'level': fields.function(_get_level, string='Level', method=True, store=True, type='integer'), @@ -219,9 +218,7 @@ class account_move(osv.osv): _inherit = "account.move" _columns = { 'name':fields.char('Name', size=256, required=True, readonly=True, states={'draft':[('readonly',False)]}), - 'narration':fields.text('Narration', readonly=True, select=True, states={'draft':[('readonly',False)]}), } - account_move() class res_currency(osv.osv): @@ -245,4 +242,4 @@ class account_account_template(osv.osv): account_account_template() -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/account_voucher/account_view.xml b/addons/account_voucher/account_view.xml index b415f1af0b9..62333c56873 100755 --- a/addons/account_voucher/account_view.xml +++ b/addons/account_voucher/account_view.xml @@ -1,61 +1,6 @@ - - account.move.type.form.inherit2 - - account.move - form - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
account.form1 diff --git a/addons/auction/i18n/pt_BR.po b/addons/auction/i18n/pt_BR.po index 9cd95c4bc2e..c6a12f8f1aa 100644 --- a/addons/auction/i18n/pt_BR.po +++ b/addons/auction/i18n/pt_BR.po @@ -7,13 +7,13 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.0\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2009-08-28 16:01+0000\n" -"PO-Revision-Date: 2010-01-23 02:16+0000\n" +"PO-Revision-Date: 2010-02-01 18:21+0000\n" "Last-Translator: Holverat Bortolossi \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-01-24 04:50+0000\n" +"X-Launchpad-Export-Date: 2010-02-02 04:48+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: auction @@ -150,74 +150,74 @@ msgstr "Antigo/pequeno produto de cobre ou estanho" #. module: auction #: field:auction.lots,paid_ach:0 msgid "Buyer invoice reconciled" -msgstr "" +msgstr "Fatura do comprador foi reconciliada" #. module: auction #: model:ir.actions.wizard,name:auction.auction_wizard_able_taken msgid "Mark as taken away" -msgstr "" +msgstr "Marcar como retirado" #. module: auction #: xsl:report.auction.seller.list:0 msgid "Inv." -msgstr "" +msgstr "Inv." #. module: auction #: field:auction.deposit.cost,amount:0 msgid "Amount" -msgstr "" +msgstr "Valor" #. module: auction #: model:ir.actions.act_window,name:auction.action_deposit_border #: model:ir.ui.menu,name:auction.menu_auction_deposit_border msgid "Deposit border" -msgstr "" +msgstr "Limite de Depósito" #. module: auction #: xsl:report.auction.deposit:0 msgid "VAT" -msgstr "" +msgstr "Imposto sobre Valor Agregado" #. module: auction #: view:auction.lots:0 msgid "Statements" -msgstr "" +msgstr "Demonstrações" #. module: auction #: xsl:flagey.huissier:0 msgid "Adjudication" -msgstr "" +msgstr "Conceder" #. module: auction #: field:auction.dates,account_analytic_id:0 msgid "Analytic Account" -msgstr "" +msgstr "Conta analítica" #. module: auction #: selection:auction.lot.category,aie_categ:0 msgid "Unclassifieds" -msgstr "" +msgstr "Não classificados" #. module: auction #: model:ir.ui.menu,name:auction.menu_auction_dates_next_by_auction msgid "Objects by Auction" -msgstr "" +msgstr "Objetos em leilão" #. module: auction #: field:auction.lots,lot_num:0 #: field:report.unclassified.objects,lot_num:0 msgid "List Number" -msgstr "" +msgstr "Número de lista" #. module: auction #: field:auction.deposit.cost,name:0 msgid "Cost Name" -msgstr "" +msgstr "Nome do custo" #. module: auction #: selection:auction.lot.category,aie_categ:0 msgid "Antiques" -msgstr "" +msgstr "Antiguidades" #. module: auction #: view:auction.dates:0 @@ -227,17 +227,17 @@ msgstr "" #. module: auction #: field:report.auction.estimation.adj.category,lot_type:0 msgid "Object Type" -msgstr "" +msgstr "Tipo de objeto" #. module: auction #: wizard_field:auction.lots.sms_send,init,text:0 msgid "SMS Message" -msgstr "" +msgstr "Mensagem SMS" #. module: auction #: selection:auction.lot.category,aie_categ:0 msgid "Contemporary Art" -msgstr "" +msgstr "Arte contemporânea" #. module: auction #: view:auction.lots:0 @@ -248,7 +248,7 @@ msgstr "" #. module: auction #: field:report.auction.view,nseller:0 msgid "No of sellers" -msgstr "" +msgstr "Nenhum vendedor" #. module: auction #: selection:auction.lots.send.aie,date_ask,lang:0 @@ -269,7 +269,7 @@ msgstr "" #. module: auction #: field:report.auction.view2,obj_margin_procent:0 msgid "Net margin (%)" -msgstr "" +msgstr "Margem líquida (%)" #. module: auction #: selection:auction.lots,state:0 @@ -277,12 +277,12 @@ msgstr "" #: selection:report.seller.auction,state:0 #: selection:report.unclassified.objects,state:0 msgid "Unsold" -msgstr "" +msgstr "Não vendidos" #. module: auction #: view:auction.dates:0 msgid "Dates" -msgstr "" +msgstr "Datas" #. module: auction #: constraint:product.template:0 @@ -294,13 +294,13 @@ msgstr "" #. module: auction #: rml:auction.total.rml:0 msgid "Items" -msgstr "" +msgstr "Itens" #. module: auction #: model:account.tax,name:auction.auction_tax5 #: field:auction.dates,seller_costs:0 msgid "Seller Costs" -msgstr "" +msgstr "Custos do Vendedor" #. module: auction #: view:auction.bid:0 @@ -310,22 +310,22 @@ msgstr "" #: model:ir.actions.report.xml,name:auction.bid_auction #: model:ir.ui.menu,name:auction.menu_action_bid_open msgid "Bids" -msgstr "" +msgstr "Lances" #. module: auction #: model:ir.ui.menu,name:auction.auction_all_objects_menu msgid "All objects" -msgstr "" +msgstr "Todos Objetos" #. module: auction #: model:ir.actions.act_window,name:auction.action_auction_buyer_reporting_all2 msgid "Buyer's auction for all months" -msgstr "" +msgstr "Leilão do comprador para todos os meses" #. module: auction #: field:auction.bid,contact_tel:0 msgid "Contact" -msgstr "" +msgstr "Contato" #. module: auction #: field:report.auction.view,obj_ret:0 @@ -337,7 +337,7 @@ msgstr "" #. module: auction #: wizard_button:auction.lots.sms_send,init,send:0 msgid "Send SMS" -msgstr "" +msgstr "Enviar SMS" #. module: auction #: selection:auction.lot.category,aie_categ:0 @@ -347,38 +347,38 @@ msgstr "" #. module: auction #: field:auction.lots,sel_inv_id:0 msgid "Seller Invoice" -msgstr "" +msgstr "Fatura do Vendedor" #. module: auction #: view:auction.dates:0 msgid "Commissions" -msgstr "" +msgstr "Comissões" #. module: auction #: wizard_button:auction.lots.numerotate,search,init:0 msgid "Back" -msgstr "" +msgstr "Voltar" #. module: auction #: model:ir.actions.act_window,name:auction.action_all_objects_unsold msgid "Unsold objects" -msgstr "" +msgstr "Objetos não vendidos" #. module: auction #: model:ir.ui.menu,name:auction.menu_all_objects_sold1 msgid "Sold Objects" -msgstr "" +msgstr "Objetos vendidos" #. module: auction #: view:report.buyer.auction2:0 #: view:report.seller.auction2:0 msgid "Sum net margin" -msgstr "" +msgstr "Somar margem líquida" #. module: auction #: view:auction.deposit:0 msgid "Deposit Border Form" -msgstr "" +msgstr "Formuálio de Limite de Depósito" #. module: auction #: field:auction.bid,bid_lines:0 @@ -386,12 +386,12 @@ msgstr "" #: rml:bids.lots:0 #: model:ir.model,name:auction.model_auction_bid_line msgid "Bid" -msgstr "" +msgstr "Lance" #. module: auction #: wizard_field:auction.lots.send.aie,date_ask,lang:0 msgid "Langage" -msgstr "" +msgstr "Idioma" #. module: auction #: field:auction.bid_line,lot_id:0 @@ -399,7 +399,7 @@ msgstr "" #: view:auction.lots:0 #: model:ir.model,name:auction.model_auction_lots msgid "Object" -msgstr "" +msgstr "Objeto" #. module: auction #: model:ir.actions.wizard,name:auction.wizard_map_user @@ -409,7 +409,7 @@ msgstr "" #. module: auction #: wizard_field:auction.lots.numerotate,search,lot_num:0 msgid "Inventory Number" -msgstr "" +msgstr "Número de inventário" #. module: auction #: wizard_button:auction.lots.buyer_map,check,end:0 @@ -417,12 +417,12 @@ msgstr "" #: wizard_button:auction.lots.numerotate,search,end:0 #: wizard_button:auction.lots.numerotate_cont,init,end:0 msgid "Exit" -msgstr "" +msgstr "Sair" #. module: auction #: field:report.buyer.auction2,net_revenue:0 msgid "Net Revenue" -msgstr "" +msgstr "Receita Líquida" #. module: auction #: view:report.buyer.auction:0 @@ -437,17 +437,17 @@ msgstr "" #: field:report.seller.auction,state:0 #: field:report.unclassified.objects,state:0 msgid "Status" -msgstr "" +msgstr "Situação" #. module: auction #: model:ir.actions.wizard,name:auction.wizard_sms msgid "SMS Send" -msgstr "" +msgstr "Enviar SMS" #. module: auction #: selection:auction.lot.category,aie_categ:0 msgid "Antique/Cartoons" -msgstr "" +msgstr "Antigo/Desenhos" #. module: auction #: view:auction.lots:0 @@ -455,42 +455,42 @@ msgstr "" #: selection:report.seller.auction,state:0 #: selection:report.unclassified.objects,state:0 msgid "Sold" -msgstr "" +msgstr "Vendido" #. module: auction #: constraint:hr.employee:0 msgid "Error ! You cannot create recursive Hierarchy of Employees." -msgstr "" +msgstr "Erro! Você não pode criar hierarquia recursiva de funcionários." #. module: auction #: field:auction.bid_line,name:0 msgid "Bid date" -msgstr "" +msgstr "Data do lance" #. module: auction #: field:auction.dates,acc_expense:0 msgid "Expense Account" -msgstr "" +msgstr "Conta de despesas" #. module: auction #: model:ir.ui.menu,name:auction.menu_wizard_emporte msgid "Deliveries Management" -msgstr "" +msgstr "Gerenciamento de entregas" #. module: auction #: field:auction.lots,obj_desc:0 msgid "Object Description" -msgstr "" +msgstr "Descrição do objeto" #. module: auction #: selection:auction.deposit,method:0 msgid "Contact the Seller" -msgstr "" +msgstr "Entre em contato com o vendedor" #. module: auction #: view:auction.lots:0 msgid "Auction Objects" -msgstr "" +msgstr "Leilão de Objetos" #. module: auction #: field:auction.lots,gross_revenue:0 @@ -498,12 +498,12 @@ msgstr "" #: field:report.object.encoded.manager,gross_revenue:0 #: field:report.seller.auction2,gross_revenue:0 msgid "Gross revenue" -msgstr "" +msgstr "Receita bruta" #. module: auction #: view:report.seller.auction2:0 msgid "Auction reporting2 form view" -msgstr "" +msgstr "Relatório2 de formulário de Leilão" #. module: auction #: model:ir.actions.wizard,name:auction.wizard_pay @@ -513,45 +513,45 @@ msgstr "" #. module: auction #: view:report.deposit.border:0 msgid "Depositer's statistics" -msgstr "" +msgstr "Estatísticas dos Depositantes" #. module: auction #: view:auction.lots:0 msgid "Buyer information" -msgstr "" +msgstr "Informações do Comprador" #. module: auction #: model:ir.model,name:auction.model_report_seller_auction msgid "Auction Reporting on seller view" -msgstr "" +msgstr "Relatório Leilão na visão do Vendedor" #. module: auction #: field:auction.lots,name2:0 msgid "Short Description (2)" -msgstr "" +msgstr "Descrição resumida (2)" #. module: auction #: model:ir.model,name:auction.model_auction_dates msgid "auction.dates" -msgstr "" +msgstr "leilão.datas" #. module: auction #: rml:auction.total.rml:0 #: model:ir.ui.menu,name:auction.auction_buyers_menu msgid "Buyers" -msgstr "" +msgstr "Vendedores" #. module: auction #: wizard_field:auction.pay.buy,init,amount:0 #: wizard_field:auction.pay.buy,init,amount2:0 #: wizard_field:auction.pay.buy,init,amount3:0 msgid "Amount paid" -msgstr "" +msgstr "Valor pago" #. module: auction #: model:ir.ui.menu,name:auction.menu_report_latest_doposit_tree msgid "My Latest Deposits" -msgstr "" +msgstr "Meus Últimos Depósitos" #. module: auction #: field:auction.lots,lot_est1:0 @@ -560,32 +560,32 @@ msgstr "" #: field:report.auction.view,min_est:0 #: field:report.unclassified.objects,lot_est1:0 msgid "Minimum Estimation" -msgstr "" +msgstr "Estimativa mínima" #. module: auction #: view:auction.dates:0 msgid "Accounting" -msgstr "" +msgstr "Contabilidade" #. module: auction #: model:ir.ui.menu,name:auction.menu_buyer_allmonth_view2 msgid "Buyer's Revenues" -msgstr "" +msgstr "Receitas do comprador" #. module: auction #: model:ir.model,name:auction.model_report_auction_adjudication msgid "report_auction_adjudication" -msgstr "" +msgstr "relatório_leilão_concedido" #. module: auction #: field:auction.bid_line,price:0 msgid "Maximum Price" -msgstr "" +msgstr "Preço máximo" #. module: auction #: selection:auction.lots.send.aie,date_ask,lang:0 msgid "eng" -msgstr "" +msgstr "Inglês" #. module: auction #: view:report.buyer.auction2:0 @@ -595,17 +595,17 @@ msgstr "" #. module: auction #: wizard_view:auction.lots.sms_send,init:0 msgid "Bulk SMS send" -msgstr "" +msgstr "Enviar SMS em massa" #. module: auction #: wizard_view:auction.taken,init:0 msgid "Select lots which are Sold" -msgstr "" +msgstr "Selecione lotes que são vendidos" #. module: auction #: field:auction.lots,statement_id:0 msgid "Payment" -msgstr "" +msgstr "Pagamento" #. module: auction #: selection:auction.lots.send.aie,date_ask,lang:0 @@ -615,50 +615,50 @@ msgstr "" #. module: auction #: selection:auction.lot.category,aie_categ:0 msgid "Antique/Old weapons and militaria" -msgstr "" +msgstr "Antiguidade/Armas antigas e militares" #. module: auction #: selection:auction.lot.category,aie_categ:0 msgid "Antique/Oriental Arts/Chineese furnitures" -msgstr "" +msgstr "Antiguidade/Arte Orientas/Mobiliário Chinês" #. module: auction #: selection:auction.deposit,method:0 msgid "Keep until sold" -msgstr "" +msgstr "Manter até vender" #. module: auction #: wizard_button:auction.lots.numerotate,init,choice:0 #: wizard_button:auction.lots.send.aie,init,date_ask:0 #: wizard_button:auction.lots.send.aie.results,init,date_ask:0 msgid "Continue" -msgstr "" +msgstr "Continuar" #. module: auction #: field:report.object.encoded,obj_num:0 #: field:report.object.encoded.manager,obj_num:0 msgid "# of Encoded obj." -msgstr "" +msgstr "# do Objeto Codificado" #. module: auction #: xsl:report.auction.vnd_bordereau:0 msgid "List N." -msgstr "" +msgstr "Lista N." #. module: auction #: field:report.deposit.border,moy_est:0 msgid "Avg. Est" -msgstr "" +msgstr "Média Estimada" #. module: auction #: rml:auction.total.rml:0 msgid "# of sellers:" -msgstr "" +msgstr "# dos vendedores:" #. module: auction #: constraint:product.product:0 msgid "Error: Invalid ean code" -msgstr "Error: código EAN invalido" +msgstr "Erro: Código EAN inválido" #. module: auction #: model:ir.module.module,description:auction.module_meta_information @@ -669,26 +669,31 @@ msgid "" " Delivery Management. \n" " " msgstr "" +"Este módulo fornece funcionalidade para \n" +" gerenciar artistas, artigos, vendedores, compradores e leilão.\n" +" Gerenciar ofertas, registro de objetos vendidos, pagos e não pagos.\n" +" A administração das entregas. \n" +" " #. module: auction #: rml:auction.total.rml:0 msgid "# of items taken away:" -msgstr "" +msgstr "# de itens retirados:" #. module: auction #: model:ir.ui.menu,name:auction.menu_auction_allmonth_view1 msgid "Auction's Summary" -msgstr "" +msgstr "Resumo do Leilão" #. module: auction #: rml:buyer.list:0 msgid "%)" -msgstr "" +msgstr "%)" #. module: auction #: wizard_view:auction.lots.able,init:0 msgid "Confirmation set taken away" -msgstr "" +msgstr "Confirmação marcada como retirada" #. module: auction #: wizard_field:auction.lots.make_invoice,init,objects:0 @@ -697,7 +702,7 @@ msgstr "" #: wizard_field:auction.lots.send.aie.results,date_ask,objects:0 #: field:report.deposit.border,nb_obj:0 msgid "# of objects" -msgstr "" +msgstr "# dos Objetos" #. module: auction #: field:auction.lots,lot_est2:0 @@ -706,12 +711,12 @@ msgstr "" #: field:report.auction.view,max_est:0 #: field:report.unclassified.objects,lot_est2:0 msgid "Maximum Estimation" -msgstr "" +msgstr "Estimativa Máxima" #. module: auction #: field:auction.lots,buyer_price:0 msgid "Buyer price" -msgstr "" +msgstr "Preço Comprador" #. module: auction #: model:ir.actions.wizard,name:auction.auction_wizard_payer_sel @@ -721,17 +726,17 @@ msgstr "" #. module: auction #: model:ir.actions.report.xml,name:auction.seller_form_id msgid "Seller List" -msgstr "" +msgstr "Lista de Vendedor" #. module: auction #: field:auction.lots,is_ok:0 msgid "Buyer's payment" -msgstr "" +msgstr "Pagamento do Comprador" #. module: auction #: view:auction.dates:0 msgid "Close + Create all invoices" -msgstr "" +msgstr "Fechar + Criar todas as Faturas" #. module: auction #: model:ir.actions.wizard,name:auction.auction_catalog_flagy @@ -741,48 +746,48 @@ msgstr "" #. module: auction #: field:auction.lot.history,price:0 msgid "Withdrawn price" -msgstr "" +msgstr "Preço de retirada" #. module: auction #: model:account.tax,name:auction.auction_tax4 msgid "Seller Costs1" -msgstr "" +msgstr "Custo1 do Venderor" #. module: auction #: selection:auction.lot.category,aie_categ:0 msgid "Antique/Verreries" -msgstr "" +msgstr "Antiguidade/Cristais" #. module: auction #: selection:auction.lot.category,aie_categ:0 msgid "Antique/Toys" -msgstr "" +msgstr "Antiguidade/Briquedos" #. module: auction #: field:auction.deposit,create_uid:0 #: field:auction.lots,create_uid:0 msgid "Created by" -msgstr "" +msgstr "Criado por" #. module: auction #: xsl:report.auction.ach_bordereau:0 msgid "Buyer form" -msgstr "" +msgstr "Formulário do comprador" #. module: auction #: xsl:report.auction.deposit:0 msgid "Estimate" -msgstr "" +msgstr "Estimativa" #. module: auction #: rml:auction.total.rml:0 msgid "# of buyers:" -msgstr "" +msgstr "# dos compradores:" #. module: auction #: field:auction.lots,costs:0 msgid "Indirect costs" -msgstr "" +msgstr "Custos indiretos" #. module: auction #: xsl:report.auction.ach_bordereau:0 @@ -792,12 +797,12 @@ msgstr "" #. module: auction #: constraint:account.tax.code:0 msgid "Error ! You can not create recursive accounts." -msgstr "" +msgstr "Erro ! Você não pode criar contas repetidas." #. module: auction #: model:ir.ui.menu,name:auction.auction_menu_root msgid "Auction Management" -msgstr "" +msgstr "Gerenciamento de leilão" #. module: auction #: field:auction.dates,journal_seller_id:0 @@ -807,23 +812,23 @@ msgstr "" #. module: auction #: view:report.buyer.auction:0 msgid "Auction buyer reporting tree view" -msgstr "" +msgstr "Relatório de compra do leilão exibição em árvore" #. module: auction #: model:ir.ui.menu,name:auction.menu_auction_artist msgid "Define Artists" -msgstr "" +msgstr "Definir artistas" #. module: auction #: field:auction.lot.category,name:0 msgid "Category Name" -msgstr "" +msgstr "Nome da Categoria" #. module: auction #: model:ir.ui.menu,name:auction.menu_report_auction_estimation_adj_category_manager #: model:ir.ui.menu,name:auction.menu_report_auction_estimation_adj_category_member msgid "Comparison of estimations" -msgstr "" +msgstr "Comparação das estimativas" #. module: auction #: model:ir.actions.report.xml,name:auction.v_report_barcode_lot @@ -833,34 +838,34 @@ msgstr "" #. module: auction #: wizard_button:auction.lots.numerotate,not_exist,init:0 msgid "Retry" -msgstr "" +msgstr "Repetir" #. module: auction #: field:auction.artists,birth_death_dates:0 msgid "Birth / Death dates" -msgstr "" +msgstr "Datas de Nascimento / Morte" #. module: auction #: selection:auction.lot.category,aie_categ:0 msgid "Antique/Jewelry" -msgstr "" +msgstr "Antiguidade/Jóias" #. module: auction #: model:ir.model,name:auction.model_report_unclassified_objects msgid "Unclassified objects " -msgstr "" +msgstr "objetos não classificados " #. module: auction #: wizard_button:auction.lots.numerotate_cont,init,set_number:0 #: wizard_field:auction.lots.send.aie,date_ask,numerotation:0 #: wizard_field:auction.lots.send.aie,init,numerotation:0 msgid "Numerotation" -msgstr "" +msgstr "Numerotation" #. module: auction #: model:ir.actions.act_window,name:auction.act_auction_lot_open_deposit msgid "Bordereau de dépôt" -msgstr "" +msgstr "Comprovante de depósito" #. module: auction #: xsl:report.auction.deposit:0 diff --git a/addons/base_calendar/DAV/BufferingHTTPServer.py b/addons/base_calendar/DAV/BufferingHTTPServer.py new file mode 100644 index 00000000000..37520421432 --- /dev/null +++ b/addons/base_calendar/DAV/BufferingHTTPServer.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python + +""" + Buffering HTTP Server + Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +""" + + +from utils import VERSION, AUTHOR +__version__ = VERSION +__author__ = AUTHOR + +from BaseHTTPServer import BaseHTTPRequestHandler +import os +class BufferedHTTPRequestHandler(BaseHTTPRequestHandler): + """ + Buffering HTTP Request Handler + + This class is an extension to the BaseHTTPRequestHandler + class which buffers the whole output and sends it at once + after the processing if the request is finished. + + This makes it possible to work together with some clients + which otherwise would break (e.g. cadaver) + + """ + + + def _init_buffer(self): + """initialize the buffer. + + If you override the handle() method remember to call + this (see below) + """ + self.__buffer="" + self.__outfp=os.tmpfile() + + def _append(self,s): + """ append a string to the buffer """ + self.__buffer=self.__buffer+s + + def _flush(self): + """ flush the buffer to wfile """ + self.wfile.write(self.__buffer) + self.__outfp.write(self.__buffer) + self.__outfp.flush() + self.wfile.flush() + self.__buffer="" + + def handle(self): + """ Handle a HTTP request """ + self._init_buffer() + BaseHTTPRequestHandler.handle(self) + self._flush() + + def send_header(self, keyword, value): + """Send a MIME header.""" + if self.request_version != 'HTTP/0.9': + self._append("%s: %s\r\n" % (keyword, value)) + + def end_headers(self): + """Send the blank line ending the MIME headers.""" + if self.request_version != 'HTTP/0.9': + self._append("\r\n") + + def send_response(self, code, message=None): + self.log_request(code) + + if message is None: + if self.responses.has_key(code): + message = self.responses[code][0] + else: + message = '' + + if self.request_version != 'HTTP/0.9': + self._append("%s %s %s\r\n" % + (self.protocol_version, str(code), message)) + + self.send_header('Server', self.version_string()) + self.send_header('Connection', 'close') + self.send_header('Date', self.date_time_string()) + + protocol_version="HTTP/1.1" + diff --git a/addons/base_calendar/DAV/WebDAVServer.py b/addons/base_calendar/DAV/WebDAVServer.py new file mode 100644 index 00000000000..5f409773c18 --- /dev/null +++ b/addons/base_calendar/DAV/WebDAVServer.py @@ -0,0 +1,379 @@ +""" + Python WebDAV Server. + Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +This module builds on AuthServer by implementing the standard DAV +methods. + +Subclass this class and specify an IFACE_CLASS. See example. + +""" + +DEBUG=None + +from utils import VERSION, AUTHOR +__version__ = VERSION +__author__ = AUTHOR + + +import os +import sys +import time +import socket +import string +import posixpath +import base64 +import urlparse +import urllib + +from propfind import PROPFIND +from delete import DELETE +from davcopy import COPY +from davmove import MOVE + +from string import atoi,split +from status import STATUS_CODES +from errors import * + +import BaseHTTPServer + +class DAVRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): + """Simple DAV request handler with + + - GET + - HEAD + - PUT + - OPTIONS + - PROPFIND + - PROPPATCH + - MKCOL + + It uses the resource/collection classes for serving and + storing content. + + """ + + server_version = "DAV/" + __version__ + protocol_version = 'HTTP/1.1' + + ### utility functions + def _log(self, message): + pass + + def _append(self,s): + """ write the string to wfile """ + self.wfile.write(s) + + def send_body(self,DATA,code,msg,desc,ctype='application/octet-stream',headers=None): + """ send a body in one part """ + + if not headers: + headers = {} + self.send_response(code,message=msg) + self.send_header("Connection", "keep-alive") + self.send_header("Accept-Ranges", "bytes") + + for a,v in headers.items(): + self.send_header(a,v) + + if DATA: + self.send_header("Content-Length", str(len(DATA))) + self.send_header("Content-Type", ctype) + else: + self.send_header("Content-Length", "0") + + self.end_headers() + if DATA: + self._append(DATA) + + def send_body_chunks(self,DATA,code,msg,desc,ctype='text/xml; encoding="utf-8"'): + """ send a body in chunks """ + + self.responses[207]=(msg,desc) + self.send_response(code,message=msg) + self.send_header("Content-type", ctype) + self.send_header("Connection", "keep-alive") + self.send_header("Transfer-Encoding", "chunked") + self.end_headers() + self._append(hex(len(DATA))[2:]+"\r\n") + self._append(DATA) + self._append("\r\n") + self._append("0\r\n") + self._append("\r\n") + + ### HTTP METHODS + + def do_OPTIONS(self): + """return the list of capabilities """ + self.send_response(200) + self.send_header("Allow", "GET, HEAD, COPY, MOVE, POST, PUT, PROPFIND, PROPPATCH, OPTIONS, MKCOL, DELETE, TRACE") + self.send_header("Content-Type", "text/plain") + self.send_header("Connection", "keep-alive") + self.send_header("DAV", "1") + self.end_headers() + + def do_PROPFIND(self): + + dc=self.IFACE_CLASS + # read the body + body=None + if self.headers.has_key("Content-Length"): + l=self.headers['Content-Length'] + body=self.rfile.read(atoi(l)) + alt_body = """ + + + + + + + + + + """ + #self.wfile.write(body) + + # which Depth? + if self.headers.has_key('Depth'): + d=self.headers['Depth'] + else: + d="infinity" + + uri=self.geturi() + pf=PROPFIND(uri,dc,d) + + if body: + pf.read_propfind(body) + + try: + DATA=pf.createResponse() + DATA=DATA+"\n" + # print "Data:", DATA + except DAV_NotFound,(ec,dd): + return self.send_notFound(dd, uri) + except DAV_Error, (ec,dd): + return self.send_error(ec,dd) + + self.send_body_chunks(DATA,207,"Multi-Status","Multiple responses") + + def geturi(self): + buri = self.IFACE_CLASS.baseuri + if buri[-1] == '/': + return urllib.unquote(buri[:-1]+self.path) + else: + return urllib.unquote(buri+self.path) + + def do_GET(self): + """Serve a GET request.""" + dc=self.IFACE_CLASS + uri=self.geturi() + + # get the last modified date + try: + lm=dc.get_prop(uri,"DAV:","getlastmodified") + except: + lm="Sun, 01 Dec 2014 00:00:00 GMT" # dummy! + headers={"Last-Modified":lm , "Connection": "keep-alive"} + + # get the content type + try: + ct=dc.get_prop(uri,"DAV:","getcontenttype") + except: + ct="application/octet-stream" + + # get the data + try: + data=dc.get_data(uri) + except DAV_Error, (ec,dd): + self.send_status(ec) + return + + # send the data + self.send_body(data,200,"OK","OK",ct,headers) + + def do_HEAD(self): + """ Send a HEAD response """ + dc=self.IFACE_CLASS + uri=self.geturi() + + # get the last modified date + try: + lm=dc.get_prop(uri,"DAV:","getlastmodified") + except: + lm="Sun, 01 Dec 2014 00:00:00 GMT" # dummy! + + headers={"Last-Modified":lm, "Connection": "keep-alive"} + + # get the content type + try: + ct=dc.get_prop(uri,"DAV:","getcontenttype") + except: + ct="application/octet-stream" + + try: + data=dc.get_data(uri) + headers["Content-Length"]=str(len(data)) + except DAV_NotFound: + self.send_body(None,404,"Not Found","") + return + + self.send_body(None,200,"OK","OK",ct,headers) + + def do_POST(self): + self.send_error(404,"File not found") + + def do_MKCOL(self): + """ create a new collection """ + + dc=self.IFACE_CLASS + uri=self.geturi() + try: + res = dc.mkcol(uri) + if res: + self.send_body(None,201,"Created",'') + else: + self.send_body(None,415,"Cannot create",'') + #self.send_header("Connection", "keep-alive") + # Todo: some content, too + except DAV_Error, (ec,dd): + self.send_body(None,int(ec),dd,dd) + + def do_DELETE(self): + """ delete an resource """ + dc=self.IFACE_CLASS + uri=self.geturi() + dl=DELETE(uri,dc) + if dc.is_collection(uri): + res=dl.delcol() + else: + res=dl.delone() + + if res: + self.send_status(207,body=res) + else: + self.send_status(204) + + def do_PUT(self): + dc=self.IFACE_CLASS + + # read the body + body=None + if self.headers.has_key("Content-Length"): + l=self.headers['Content-Length'] + body=self.rfile.read(atoi(l)) + uri=self.geturi() + + ct=None + if self.headers.has_key("Content-Type"): + ct=self.headers['Content-Type'] + try: + dc.put(uri,body,ct) + except DAV_Error, (ec,dd): + self.send_status(ec) + return + self.send_status(201) + + def do_COPY(self): + """ copy one resource to another """ + try: + self.copymove(COPY) + except DAV_Error, (ec,dd): + self.send_status(ec) + + def do_MOVE(self): + """ move one resource to another """ + try: + self.copymove(MOVE) + except DAV_Error, (ec,dd): + self.send_status(ec) + + def copymove(self,CLASS): + """ common method for copying or moving objects """ + dc=self.IFACE_CLASS + + # get the source URI + source_uri=self.geturi() + + # get the destination URI + dest_uri=self.headers['Destination'] + dest_uri=urllib.unquote(dest_uri) + + # Overwrite? + overwrite=1 + result_code=204 + if self.headers.has_key("Overwrite"): + if self.headers['Overwrite']=="F": + overwrite=None + result_code=201 + + # instanciate ACTION class + cp=CLASS(dc,source_uri,dest_uri,overwrite) + + # Depth? + d="infinity" + if self.headers.has_key("Depth"): + d=self.headers['Depth'] + + if d!="0" and d!="infinity": + self.send_status(400) + return + + if d=="0": + res=cp.single_action() + self.send_status(res) + return + + # now it only can be "infinity" but we nevertheless check for a collection + if dc.is_collection(source_uri): + try: + res=cp.tree_action() + except DAV_Error, (ec,dd): + self.send_status(ec) + return + else: + try: + res=cp.single_action() + except DAV_Error, (ec,dd): + self.send_status(ec) + return + + if res: + self.send_body_chunks(res,207,STATUS_CODES[207],STATUS_CODES[207], + ctype='text/xml; charset="utf-8"') + else: + self.send_status(result_code) + + def get_userinfo(self,user,pw): + """ Dummy method which lets all users in """ + + return 1 + + def send_status(self,code=200,mediatype='text/xml; charset="utf-8"', \ + msg=None,body=None): + + if not msg: msg=STATUS_CODES[code] + self.send_body(body,code,STATUS_CODES[code],msg,mediatype) + + def send_notFound(self,descr,uri): + body = """ + + %s + + %s + + """ + return self.send_status(404,descr, body=body % (uri,descr)) diff --git a/addons/base_calendar/DAV/__init__.py b/addons/base_calendar/DAV/__init__.py new file mode 100644 index 00000000000..10abdb40014 --- /dev/null +++ b/addons/base_calendar/DAV/__init__.py @@ -0,0 +1,20 @@ +""" + python davserver + Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" + + diff --git a/addons/base_calendar/DAV/constants.py b/addons/base_calendar/DAV/constants.py new file mode 100644 index 00000000000..e858ac519c2 --- /dev/null +++ b/addons/base_calendar/DAV/constants.py @@ -0,0 +1,18 @@ +""" + +constants definition + + +""" + +# definition for resourcetype +COLLECTION=1 +OBJECT=None +DAV_PROPS=['creationdate', 'displayname', 'getcontentlanguage', 'getcontentlength', 'getcontenttype', 'getetag', 'getlastmodified', 'lockdiscovery', 'resourcetype', 'source', 'supportedlock'] + + +# Request classes in propfind + +RT_ALLPROP=1 +RT_PROPNAME=2 +RT_PROP=3 diff --git a/addons/base_calendar/DAV/davcmd.py b/addons/base_calendar/DAV/davcmd.py new file mode 100644 index 00000000000..5c6be3ff9f7 --- /dev/null +++ b/addons/base_calendar/DAV/davcmd.py @@ -0,0 +1,218 @@ +""" + +davcmd.py +--------- + +containts commands like copy, move, delete for normal +resources and collections + +""" + +from string import split,replace,joinfields +import urlparse + +from utils import create_treelist, is_prefix +from errors import * + +def deltree(dc,uri,exclude={}): + """ delete a tree of resources + + dc -- dataclass to use + uri -- root uri to delete + exclude -- an optional list of uri:error_code pairs which should not + be deleted. + + returns dict of uri:error_code tuples from which + another method can create a multistatus xml element. + + Also note that we only know Depth=infinity thus we don't have + to test for it. + + """ + + tlist=create_treelist(dc,uri) + result={} + + for i in range(len(tlist),0,-1): + problem_uris=result.keys() + element=tlist[i-1] + + # test here, if an element is a prefix of an uri which + # generated an error before. + # note that we walk here from childs to parents, thus + # we cannot delete a parent if a child made a problem. + # (see example in 8.6.2.1) + ok=1 + for p in problem_uris: + if is_prefix(element,p): + ok=None + break + + if not ok: continue + + # here we test for the exclude list which is the other way round! + for p in exclude.keys(): + if is_prefix(p,element): + ok=None + break + + if not ok: continue + + # now delete stuff + try: + delone(dc,element) + except DAV_Error, (ec,dd): + result[element]=ec + + return result + +def delone(dc,uri): + """ delete a single object """ + if dc.is_collection(uri): + dc.rmcol(uri) # should be empty + else: + dc.rm(uri) + +### +### COPY +### + +# helper function + +def copy(dc,src,dst): + """ only copy the element + + This is just a helper method factored out from copy and + copytree. It will not handle the overwrite or depth header. + + """ + + # destination should have been deleted before + if dc.exists(dst): raise DAV_Error, 412 + + # source should exist also + if not dc.exists(src): raise DAV_NotFound + + if dc.is_collection(src): + dc.copycol(src,dst) # an exception will be passed thru + else: + dc.copy(src,dst) # an exception will be passed thru + + +# the main functions + +def copyone(dc,src,dst,overwrite=None): + """ copy one resource to a new destination """ + + if overwrite and dc.exists(dst): + delres=deltree(dc,dst) + else: + delres={} + + # if we cannot delete everything, then do not copy! + if delres: return delres + + try: + copy(dc,src,dst) # pass thru exceptions + except DAV_Error, (ec,dd): + return ec + +def copytree(dc,src,dst,overwrite=None): + """ copy a tree of resources to another location + + dc -- dataclass to use + src -- src uri from where to copy + dst -- dst uri + overwrite -- if 1 then delete dst uri before + + returns dict of uri:error_code tuples from which + another method can create a multistatus xml element. + + """ + + + # first delete the destination resource + if overwrite and dc.exists(dst): + delres=deltree(dc,dst) + else: + delres={} + + # if we cannot delete everything, then do not copy! + if delres: return delres + + # get the tree we have to copy + tlist=create_treelist(dc,src) + result={} + + # prepare destination URIs (get the prefix) + dpath=urlparse.urlparse(dst)[2] + + for element in tlist: + problem_uris=result.keys() + + # now URIs get longer and longer thus we have + # to test if we had a parent URI which we were not + # able to copy in problem_uris which is the prefix + # of the actual element. If it is, then we cannot + # copy this as well but do not generate another error. + ok=1 + for p in problem_uris: + if is_prefix(p,element): + ok=None + break + + if not ok: continue + + # now create the destination URI which corresponds to + # the actual source URI. -> actual_dst + # ("subtract" the base src from the URI and prepend the + # dst prefix to it.) + esrc=replace(element,src,"") + actual_dst=dpath+esrc + + # now copy stuff + try: + copy(dc,element,actual_dst) + except DAV_Error, (ec,dd): + result[element]=ec + + return result + + + +### +### MOVE +### + + +def moveone(dc,src,dst,overwrite=None): + """ move a single resource + + This is done by first copying it and then deleting + the original. + """ + + # first copy it + copyone(dc,src,dst,overwrite) + + # then delete it + dc.rm(src) + +def movetree(dc,src,dst,overwrite=None): + """ move a collection + + This is done by first copying it and then deleting + the original. + + PROBLEM: if something did not copy then we have a problem + when deleting as the original might get deleted! + """ + + # first copy it + res=copytree(dc,src,dst,overwrite) + + # then delete it + res=deltree(dc,src,exclude=res) + + return res + diff --git a/addons/base_calendar/DAV/davcopy.py b/addons/base_calendar/DAV/davcopy.py new file mode 100755 index 00000000000..ec44931b107 --- /dev/null +++ b/addons/base_calendar/DAV/davcopy.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python + +""" + python davserver + Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +""" + + +from xml.dom import ext +from xml.dom.Document import Document + +import sys +import string +import urlparse +import urllib +from StringIO import StringIO + +import utils +from constants import COLLECTION, OBJECT, DAV_PROPS, RT_ALLPROP, RT_PROPNAME, RT_PROP +from errors import * +from utils import create_treelist, quote_uri, gen_estring + +class COPY: + """ copy resources and eventually create multistatus responses + + This module implements the COPY class which is responsible for + copying resources. Usually the normal copy work is done in the + interface class. This class only creates error messages if error + occur. + + """ + + + def __init__(self,dataclass,src_uri,dst_uri,overwrite): + self.__dataclass=dataclass + self.__src=src_uri + self.__dst=dst_uri + self.__overwrite=overwrite + + + def single_action(self): + """ copy a normal resources. + + We try to copy it and return the result code. + This is for Depth==0 + + """ + + dc=self.__dataclass + base=self.__src + + ### some basic tests + # test if dest exists and overwrite is false + if dc.exists(self.__dst) and not self.__overwrite: raise DAV_Error, 412 + # test if src and dst are the same + # (we assume that both uris are on the same server!) + ps=urlparse.urlparse(self.__src)[2] + pd=urlparse.urlparse(self.__dst)[2] + if ps==pd: raise DAV_Error, 403 + + return dc.copyone(self.__src,self.__dst,self.__overwrite) + + #return copyone(dc,self.__src,self.__dst,self.__overwrite) + + def tree_action(self): + """ copy a tree of resources (a collection) + + Here we return a multistatus xml element. + + """ + dc=self.__dataclass + base=self.__src + + ### some basic tests + # test if dest exists and overwrite is false + if dc.exists(self.__dst) and not self.__overwrite: raise DAV_Error, 412 + # test if src and dst are the same + # (we assume that both uris are on the same server!) + ps=urlparse.urlparse(self.__src)[2] + pd=urlparse.urlparse(self.__dst)[2] + if ps==pd: raise DAV_Error, 403 + + + result=dc.copytree(self.__src,self.__dst,self.__overwrite) + #result=copytree(dc,self.__src,self.__dst,self.__overwrite) + + if not result: return None + + ### + ### create the multistatus XML element + ### (this is also the same as in delete.py. + ### we might make a common method out of it) + ### + + doc = Document(None) + ms=doc.createElement("D:multistatus") + ms.setAttribute("xmlns:D","DAV:") + doc.appendChild(ms) + + for el,ec in result.items(): + re=doc.createElement("D:response") + hr=doc.createElement("D:href") + st=doc.createElement("D:status") + huri=doc.createTextNode(quote_uri(el)) + t=doc.createTextNode(gen_estring(ec)) + st.appendChild(t) + hr.appendChild(huri) + re.appendChild(hr) + re.appendChild(st) + ms.appendChild(re) + + sfile=StringIO() + ext.PrettyPrint(doc,stream=sfile) + s=sfile.getvalue() + sfile.close() + return s + diff --git a/addons/base_calendar/DAV/davmove.py b/addons/base_calendar/DAV/davmove.py new file mode 100755 index 00000000000..78c1e649389 --- /dev/null +++ b/addons/base_calendar/DAV/davmove.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python + +""" + python davserver + Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +""" + + +import sys +import string +import urlparse +import urllib +from StringIO import StringIO + +import utils +from constants import COLLECTION, OBJECT, DAV_PROPS +from constants import RT_ALLPROP, RT_PROPNAME, RT_PROP +from errors import * +from utils import create_treelist, quote_uri, gen_estring, make_xmlresponse +from davcmd import moveone, movetree + +class MOVE: + """ move resources and eventually create multistatus responses + + This module implements the MOVE class which is responsible for + moving resources. + + MOVE is implemented by a COPY followed by a DELETE of the old + resource. + + """ + + + def __init__(self,dataclass,src_uri,dst_uri,overwrite): + self.__dataclass=dataclass + self.__src=src_uri + self.__dst=dst_uri + self.__overwrite=overwrite + + + def single_action(self): + """ move a normal resources. + + We try to move it and return the result code. + This is for Depth==0 + + """ + + dc=self.__dataclass + base=self.__src + + ### some basic tests + # test if dest exists and overwrite is false + if dc.exists(self.__dst) and not self.__overwrite: raise DAV_Error, 412 + # test if src and dst are the same + # (we assume that both uris are on the same server!) + ps=urlparse.urlparse(self.__src)[2] + pd=urlparse.urlparse(self.__dst)[2] + if ps==pd: raise DAV_Error, 403 + + return dc.moveone(self.__src,self.__dst,self.__overwrite) + + def tree_action(self): + """ move a tree of resources (a collection) + + Here we return a multistatus xml element. + + """ + dc=self.__dataclass + base=self.__src + + ### some basic tests + # test if dest exists and overwrite is false + if dc.exists(self.__dst) and not self.__overwrite: raise DAV_Error, 412 + # test if src and dst are the same + # (we assume that both uris are on the same server!) + ps=urlparse.urlparse(self.__src)[2] + pd=urlparse.urlparse(self.__dst)[2] + if ps==pd: raise DAV_Error, 403 + + result=dc.movetree(self.__src,self.__dst,self.__overwrite) + if not result: return None + + # create the multistatus XML element + return make_xmlresponse(result) + diff --git a/addons/base_calendar/DAV/delete.py b/addons/base_calendar/DAV/delete.py new file mode 100755 index 00000000000..ed017e53b6d --- /dev/null +++ b/addons/base_calendar/DAV/delete.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python + +""" + + python davserver + Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +""" +import os +import string +import urllib +from StringIO import StringIO + +from status import STATUS_CODES +from utils import gen_estring, quote_uri, make_xmlresponse +from davcmd import deltree + +class DELETE: + + def __init__(self,uri,dataclass): + self.__dataclass=dataclass + self.__uri=uri + + def delcol(self): + """ delete a collection """ + + dc=self.__dataclass + result=dc.deltree(self.__uri) + + if not len(result.items()): + return None # everything ok + + # create the result element + return make_xmlresponse(result) + + def delone(self): + """ delete a resource """ + + dc=self.__dataclass + result=dc.delone(self.__uri) + + if not result: return None + if not len(result.items()): + return None # everything ok + + # create the result element + return make_xmlresponse(result) + diff --git a/addons/base_calendar/DAV/errors.py b/addons/base_calendar/DAV/errors.py new file mode 100755 index 00000000000..9c0e7667913 --- /dev/null +++ b/addons/base_calendar/DAV/errors.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +""" + + Exceptions for the DAVserver implementation + +""" + +class DAV_Error(Exception): + """ in general we can have the following arguments: + + 1. the error code + 2. the error result element, e.g. a element + """ + + def __init__(self,*args): + if len(args)==1: + self.args=(args[0],"") + else: + self.args=args + +class DAV_Secret(DAV_Error): + """ the user is not allowed to know anything about it + + returning this for a property value means to exclude it + from the response xml element. + """ + + def __init__(self): + DAV_Error.__init__(self,0) + pass + +class DAV_NotFound(DAV_Error): + """ a requested property was not found for a resource """ + + def __init__(self,*args): + if len(args): + if isinstance(args[0],list): + stre = "Path %s not found!"%('/'.join(args[0])) + else: + stre = args[0] + DAV_Error.__init__(self,404,stre) + else: + DAV_Error.__init__(self,404) + + pass + +class DAV_Forbidden(DAV_Error): + """ a method on a resource is not allowed """ + + def __init__(self,*args): + if len(args): + DAV_Error.__init__(self,403,args[0]) + else: + DAV_Error.__init__(self,403) + pass + diff --git a/addons/base_calendar/DAV/iface.py b/addons/base_calendar/DAV/iface.py new file mode 100644 index 00000000000..c8f5fe2594d --- /dev/null +++ b/addons/base_calendar/DAV/iface.py @@ -0,0 +1,263 @@ +""" + +basic interface class + +use this for subclassing when writing your own interface +class. + +""" + +from errors import * + +import time +from string import lower + +class dav_interface: + """ interface class for implementing DAV servers """ + + ### defined properties (modify this but let the DAV stuff there!) + ### the format is namespace: [list of properties] + + PROPS={"DAV:" : ('creationdate', + 'displayname', + 'getcontentlanguage', + 'getcontentlength', + 'getcontenttype', + 'getetag', + 'getlastmodified', + 'lockdiscovery', + 'resourcetype', + 'source', + 'supportedlock'), + "NS2" : ("p1","p2") + } + + # here we define which methods handle which namespace + # the first item is the namespace URI and the second one + # the method prefix + # e.g. for DAV:getcontenttype we call dav_getcontenttype() + M_NS={"DAV:" : "_get_dav", + "NS2" : "ns2" } + + def get_propnames(self,uri): + """ return the property names allowed for the given URI + + In this method we simply return the above defined properties + assuming that they are valid for any resource. + You can override this in order to return a different set + of property names for each resource. + + """ + return self.PROPS + + def get_prop2(self,uri,ns,pname): + """ return the value of a property + """ + if lower(ns)=="dav:": return self.get_dav(uri,pname) + + raise DAV_NotFound + + def get_prop(self,uri,ns,propname): + """ return the value of a given property + + uri -- uri of the object to get the property of + ns -- namespace of the property + pname -- name of the property + """ + if self.M_NS.has_key(ns): + prefix=self.M_NS[ns] + else: + print "No namespace:",ns, "( for prop:", propname,")" + raise DAV_NotFound + mname=prefix+"_"+propname + if not hasattr(self,mname): + raise DAV_NotFound + + try: + m=getattr(self,mname) + r=m(uri) + return r + except AttributeError, e: + print 'Property %s not supported' % propname + print "Exception:", e + raise DAV_NotFound + + ### + ### DATA methods (for GET and PUT) + ### + + def get_data(self,uri): + """ return the content of an object + + return data or raise an exception + + """ + raise DAV_NotFound + + def put(self,uri,data): + """ write an object to the repository + + return a result code or raise an exception + """ + + raise DAV_Forbidden + + ### + ### Methods for DAV properties + ### + + def _get_dav_creationdate(self,uri): + """ return the creationdate of a resource """ + d=self.get_creationdate(uri) + # format it + if isinstance(d, int) or isinstance(d, float): + d = time.localtimetime(d) + return time.strftime("%Y-%m-%dT%H:%M:%S%Z",d) + + def _get_dav_getlastmodified(self,uri): + """ return the last modified date of a resource """ + d=self.get_lastmodified(uri) + if isinstance(d, int) or isinstance(d, float): + d = time.localtime(d) + # format it + return time.asctime(d) + + + ### + ### OVERRIDE THESE! + ### + + def get_creationdate(self,uri): + """ return the creationdate of the resource """ + return time.time() + + def get_lastmodified(self,uri): + """ return the last modification date of the resource """ + return time.time() + + + ### + ### COPY MOVE DELETE + ### + + ### methods for deleting a resource + + def rmcol(self,uri): + """ delete a collection + + This should not delete any children! This is automatically done + before by the DELETE class in DAV/delete.py + + return a success code or raise an exception + + """ + raise DAV_NotFound + + def rm(self,uri): + """ delete a single resource + + return a success code or raise an exception + + """ + raise DAV_NotFound + + """ + + COPY/MOVE HANDLER + + These handler are called when a COPY or MOVE method is invoked by + a client. In the default implementation it works as follows: + + - the davserver receives a COPY/MOVE method + - the davcopy or davmove module will be loaded and the corresponding + class will be initialized + - this class parses the query and decides which method of the interface class + to call: + + copyone for a single resource to copy + copytree for a tree to copy (collection) + (the same goes for move of course). + + - the interface class has now two options: + 1. to handle the action directly (e.g. cp or mv on filesystems) + 2. to let it handle via the copy/move methods in davcmd. + + ad 1) The first approach can be used when we know that no error can + happen inside a tree or when the action can exactly tell which + element made which error. We have to collect these and return + it in a dict of the form {uri: error_code, ...} + + ad 2) The copytree/movetree/... methods of davcmd.py will do the recursion + themselves and call for each resource the copy/move method of the + interface class. Thus method will then only act on a single resource. + (Thus a copycol on a normal unix filesystem actually only needs to do + an mkdir as the content will be copied by the davcmd.py function. + The davcmd.py method will also automatically collect all errors and + return the dictionary described above. + When you use 2) you also have to implement the copy() and copycol() + methods in your interface class. See the example for details. + + To decide which approach is the best you have to decide if your application + is able to generate errors inside a tree. E.g. a function which completely + fails on a tree if one of the tree's childs fail is not what we need. Then + 2) would be your way of doing it. + Actually usually 2) is the better solution and should only be replaced by + 1) if you really need it. + + The remaining question is if we should do the same for the DELETE method. + + """ + + ### MOVE handlers + + def moveone(self,src,dst,overwrite): + """ move one resource with Depth=0 """ + return moveone(self,src,dst,overwrite) + + def movetree(self,src,dst,overwrite): + """ move a collection with Depth=infinity """ + return movetree(self,src,dst,overwrite) + + ### COPY handlers + + def copyone(self,src,dst,overwrite): + """ copy one resource with Depth=0 """ + return copyone(self,src,dst,overwrite) + + def copytree(self,src,dst,overwrite): + """ copy a collection with Depth=infinity """ + return copytree(self,src,dst,overwrite) + + + ### low level copy methods (you only need these for method 2) + def copy(self,src,dst): + """ copy a resource with depth==0 + + You don't need to bother about overwrite or not. + This has been done already. + + return a success code or raise an exception if something fails + """ + return 201 + + + def copycol(self,src,dst): + """ copy a resource with depth==infinity + + You don't need to bother about overwrite or not. + This has been done already. + + return a success code or raise an exception if something fails + """ + return 201 + + ### some utility functions you need to implement + + def exists(self,uri): + """ return 1 or None depending on if a resource exists """ + return None # no + + def is_collection(self,uri): + """ return 1 or None depending on if a resource is a collection """ + return None # no + diff --git a/addons/base_calendar/DAV/propfind.py b/addons/base_calendar/DAV/propfind.py new file mode 100755 index 00000000000..0fb9c229c03 --- /dev/null +++ b/addons/base_calendar/DAV/propfind.py @@ -0,0 +1,372 @@ +#!/usr/bin/env python + +""" + python davserver + Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +""" + + +from xml.dom import ext +from xml.dom.Document import Document + +import sys +import string +import urlparse +import urllib +from StringIO import StringIO + +import utils +from constants import COLLECTION, OBJECT, DAV_PROPS, RT_ALLPROP, RT_PROPNAME, RT_PROP +from errors import * + +def utf8str(st): + if isinstance(st,unicode): + return st.encode('utf8') + else: + return str(st) + +class PROPFIND: + """ parse a propfind xml element and extract props + + It will set the following instance vars: + + request_class : ALLPROP | PROPNAME | PROP + proplist : list of properties + nsmap : map of namespaces + + The list of properties will contain tuples of the form + (element name, ns_prefix, ns_uri) + + + """ + + + def __init__(self,uri,dataclass,depth): + self.request_type=None + self.nsmap={} + self.proplist={} + self.default_ns=None + self.__dataclass=dataclass + self.__depth=str(depth) + self.__uri=uri + self.use_full_urls=True + self.__has_body=None # did we parse a body? + + def read_propfind(self,xml_doc): + self.request_type,self.proplist,self.namespaces=utils.parse_propfind(xml_doc) + + # a violation of the expected logic: client (korganizer) will ask for DAV:resourcetype + # but we also have to return the http://groupdav.org/:resourcetype property! + if self.proplist.has_key('DAV:') and 'resourcetype' in self.proplist['DAV:']: + if not self.proplist.has_key('http://groupdav.org/'): + self.proplist['http://groupdav.org/'] = [] + self.proplist['http://groupdav.org/'].append('resourcetype') + if 'DAV:' in self.namespaces: #TMP + self.namespaces.append('http://groupdav.org/') + + def createResponse(self): + """ create the multistatus response + + This will be delegated to the specific method + depending on which request (allprop, propname, prop) + was found. + + If we get a PROPNAME then we simply return the list with empty + values which we get from the interface class + + If we get an ALLPROP we first get the list of properties and then + we do the same as with a PROP method. + + If the uri doesn't exist, return an xml response with a 404 status + + """ + + if not self.__dataclass.exists(self.__uri): + raise DAV_NotFound("Path %s doesn't exist" % self.__uri) + + if self.request_type==RT_ALLPROP: + return self.create_allprop() + + if self.request_type==RT_PROPNAME: + return self.create_propname() + + if self.request_type==RT_PROP: + return self.create_prop() + + # no body means ALLPROP! + return self.create_allprop() + + def create_propname(self): + """ create a multistatus response for the prop names """ + + dc=self.__dataclass + # create the document generator + doc = Document(None) + ms=doc.createElement("D:multistatus") + ms.setAttribute("xmlns:D","DAV:") + doc.appendChild(ms) + + if self.__depth=="0": + pnames=dc.get_propnames(self.__uri) + re=self.mk_propname_response(self.__uri,pnames,doc) + ms.appendChild(re) + + elif self.__depth=="1": + pnames=dc.get_propnames(self.__uri) + re=self.mk_propname_response(self.__uri,pnames,doc) + ms.appendChild(re) + + for newuri in dc.get_childs(self.__uri): + pnames=dc.get_propnames(newuri) + re=self.mk_propname_response(newuri,pnames,doc) + ms.appendChild(re) + # *** depth=="infinity" + + sfile=StringIO() + ext.PrettyPrint(doc,stream=sfile) + s=sfile.getvalue() + sfile.close() + return s + + def create_allprop(self): + """ return a list of all properties """ + self.proplist={} + self.namespaces=[] + for ns,plist in self.__dataclass.get_propnames(self.__uri).items(): + self.proplist[ns]=plist + self.namespaces.append(ns) + + return self.create_prop() + + def create_prop(self): + """ handle a request + + This will + + 1. set up the -Framework + + 2. read the property values for each URI + (which is dependant on the Depth header) + This is done by the get_propvalues() method. + + 3. For each URI call the append_result() method + to append the actual -Tag to the result + document. + + We differ between "good" properties, which have been + assigned a value by the interface class and "bad" + properties, which resulted in an error, either 404 + (Not Found) or 403 (Forbidden). + + """ + + + # create the document generator + doc = Document(None) + ms=doc.createElement("D:multistatus") + ms.setAttribute("xmlns:D","DAV:") + doc.appendChild(ms) + + if self.__depth=="0": + gp,bp=self.get_propvalues(self.__uri) + res=self.mk_prop_response(self.__uri,gp,bp,doc) + ms.appendChild(res) + + elif self.__depth=="1": + gp,bp=self.get_propvalues(self.__uri) + res=self.mk_prop_response(self.__uri,gp,bp,doc) + ms.appendChild(res) + + try: + for newuri in self.__dataclass.get_childs(self.__uri): + gp,bp=self.get_propvalues(newuri) + res=self.mk_prop_response(newuri,gp,bp,doc) + ms.appendChild(res) + except DAV_NotFound: + # If no children, never mind. + pass + + sfile=StringIO() + ext.PrettyPrint(doc,stream=sfile) + s=sfile.getvalue() + sfile.close() + return s + + + def mk_propname_response(self,uri,propnames,doc): + """ make a new result element for a PROPNAME request + + This will simply format the propnames list. + propnames should have the format {NS1 : [prop1, prop2, ...], NS2: ...} + + """ + re=doc.createElement("D:response") + + # write href information + href=doc.createElement("D:href") + if self.use_full_urls: + huri=doc.createTextNode(uri) + else: + uparts=urlparse.urlparse(uri) + fileloc=uparts[2] + huri=doc.createTextNode(urllib.quote(fileloc.encode('utf8'))) + href.appendChild(huri) + re.appendChild(href) + + ps=doc.createElement("D:propstat") + nsnum=0 + + for ns,plist in propnames.items(): + # write prop element + pr=doc.createElement("D:prop") + nsp="ns"+str(nsnum) + pr.setAttribute("xmlns:"+nsp,ns) + nsnum=nsnum+1 + + # write propertynames + for p in plist: + pe=doc.createElement(nsp+":"+p) + pr.appendChild(pe) + + ps.appendChild(pr) + + re.appendChild(ps) + + return re + + def mk_prop_response(self,uri,good_props,bad_props,doc): + """ make a new result element + + We differ between the good props and the bad ones for + each generating an extra -Node (for each error + one, that means). + + """ + re=doc.createElement("D:response") + # append namespaces to response + nsnum=0 + for nsname in self.namespaces: + re.setAttribute("xmlns:ns"+str(nsnum),nsname) + nsnum=nsnum+1 + + # write href information + href=doc.createElement("D:href") + if self.use_full_urls: + huri=doc.createTextNode(uri) + else: + uparts=urlparse.urlparse(uri) + fileloc=uparts[2] + huri=doc.createTextNode(urllib.quote(fileloc.encode('utf8'))) + href.appendChild(huri) + re.appendChild(href) + + # write good properties + if good_props and len(good_props.items()): + ps=doc.createElement("D:propstat") + + gp=doc.createElement("D:prop") + for ns in good_props.keys(): + ns_prefix="ns"+str(self.namespaces.index(ns))+":" + for p,v in good_props[ns].items(): + pe=doc.createElement(ns_prefix+str(p)) + if v == None: + pass + elif ns=='DAV:' and p=="resourcetype": + if v == 1: + ve=doc.createElement("D:collection") + pe.appendChild(ve) + elif isinstance(v,tuple) and v[1] == ns: + ve=doc.createElement(ns_prefix+v[0]) + pe.appendChild(ve) + else: + ve=doc.createTextNode(utf8str(v)) + pe.appendChild(ve) + + gp.appendChild(pe) + if gp.hasChildNodes(): + re.appendChild(ps) + ps.appendChild(gp) + s=doc.createElement("D:status") + t=doc.createTextNode("HTTP/1.1 200 OK") + s.appendChild(t) + ps.appendChild(s) + re.appendChild(ps) + + # now write the errors! + if len(bad_props.items()): + + # write a propstat for each error code + for ecode in bad_props.keys(): + ps=doc.createElement("D:propstat") + re.appendChild(ps) + bp=doc.createElement("D:prop") + ps.appendChild(bp) + + for ns in bad_props[ecode].keys(): + ns_prefix="ns"+str(self.namespaces.index(ns))+":" + + for p in bad_props[ecode][ns]: + pe=doc.createElement(ns_prefix+str(p)) + bp.appendChild(pe) + + s=doc.createElement("D:status") + t=doc.createTextNode(utils.gen_estring(ecode)) + s.appendChild(t) + ps.appendChild(s) + re.appendChild(ps) + + # return the new response element + return re + + def get_propvalues(self,uri): + """ create lists of property values for an URI + + We create two lists for an URI: the properties for + which we found a value and the ones for which we + only got an error, either because they haven't been + found or the user is not allowed to read them. + + """ + good_props={} + bad_props={} + + for (ns,plist) in self.proplist.items(): + good_props[ns]={} + bad_props={} + ec = 0 + for prop in plist: + try: + ec = 0 + r=self.__dataclass.get_prop(uri,ns,prop) + good_props[ns][prop]=r + except DAV_Error, error_code: + ec=error_code[0] + + # ignore props with error_code if 0 (invisible) + if ec==0: continue + + if bad_props.has_key(ec): + if bad_props[ec].has_key(ns): + bad_props[ec][ns].append(prop) + else: + bad_props[ec][ns]=[prop] + else: + bad_props[ec]={ns:[prop]} + + return good_props, bad_props + diff --git a/addons/base_calendar/DAV/status.py b/addons/base_calendar/DAV/status.py new file mode 100644 index 00000000000..62cb323c4fb --- /dev/null +++ b/addons/base_calendar/DAV/status.py @@ -0,0 +1,31 @@ +""" + +status codes for DAV services + + +""" + + +STATUS_CODES={ + 102: "Processing", + 200: "Ok", + 201: "Created", + 204: "No Content", + 207: "Multi-Status", + 201: "Created", + 400: "Bad Request", + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 409: "Conflict", + 412: "Precondition failed", + 423: "Locked", + 415: "Unsupported Media Type", + 507: "Insufficient Storage", + 422: "Unprocessable Entity", + 423: "Locked", + 424: "Failed Dependency", + 502: "Bad Gateway", + 507: "Insufficient Storage", + 999: "Some error in Create Method please check the data of create method" +} diff --git a/addons/base_calendar/DAV/utils.py b/addons/base_calendar/DAV/utils.py new file mode 100755 index 00000000000..c4bb4d1a4c0 --- /dev/null +++ b/addons/base_calendar/DAV/utils.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python + +""" + +UTILITIES + +- parse a propfind request body into a list of props + +""" + +from xml.dom import ext +from xml.dom.Document import Document +from xml.dom.ext.reader import PyExpat +from xml.dom import Node +from xml.dom import NodeIterator, NodeFilter + +from string import lower, split, atoi, joinfields +import urlparse +from StringIO import StringIO + +from constants import RT_ALLPROP, RT_PROPNAME, RT_PROP +from status import STATUS_CODES + +VERSION = '0.6' +AUTHOR = 'Simon Pamies ' + + +def gen_estring(ecode): + """ generate an error string from the given code """ + ec=atoi(str(ecode)) + if STATUS_CODES.has_key(ec): + return "HTTP/1.1 %s %s" %(ec,STATUS_CODES[ec]) + else: + return "HTTP/1.1 %s" %(ec) + +def parse_propfind(xml_doc): + """ parse an propfind xml file and return a list of props + + returns: + + request_type -- ALLPROP, PROPNAME, PROP + proplist -- list of properties found + namespaces -- list of namespaces found + + """ + doc = PyExpat.Reader().fromString(xml_doc) + snit = doc.createNodeIterator(doc, NodeFilter.NodeFilter.SHOW_ELEMENT, None, None) + + request_type=None + props={} + namespaces=[] + + while 1: + curr_elem = snit.nextNode() + if not curr_elem: break + ename=fname=lower(curr_elem.nodeName) + if ":" in fname: + ename=split(fname,":")[1] + if ename=="prop": request_type=RT_PROP; continue + if ename=="propfind": continue + if ename=="allprop": request_type=RT_ALLPROP; continue + if ename=="propname": request_type=RT_PROPNAME; continue + + # rest should be names of attributes + + ns = curr_elem.namespaceURI + if props.has_key(ns): + props[ns].append(ename) + else: + props[ns]=[ename] + namespaces.append(ns) + + return request_type,props,namespaces + + +def create_treelist(dataclass,uri): + """ create a list of resources out of a tree + + This function is used for the COPY, MOVE and DELETE methods + + uri - the root of the subtree to flatten + + It will return the flattened tree as list + + """ + queue=[uri] + list=[uri] + while len(queue): + element=queue[-1] + if dataclass.is_collection(element): + childs=dataclass.get_childs(element) + else: + childs=[] + if len(childs): + list=list+childs + # update queue + del queue[-1] + if len(childs): + queue=queue+childs + return list + +def is_prefix(uri1,uri2): + """ returns 1 of uri1 is a prefix of uri2 """ + if uri2[:len(uri1)]==uri1: + return 1 + else: + return None + +def quote_uri(uri): + """ quote an URL but not the protocol part """ + import urlparse + import urllib + + up=urlparse.urlparse(uri) + np=urllib.quote(up[2]) + return urlparse.urlunparse((up[0],up[1],np,up[3],up[4],up[5])) + +def get_uriparentpath(uri): + """ extract the uri path and remove the last element """ + up=urlparse.urlparse(uri) + return joinfields(split(up[2],"/")[:-1],"/") + +def get_urifilename(uri): + """ extract the uri path and return the last element """ + up=urlparse.urlparse(uri) + return split(up[2],"/")[-1] + +def get_parenturi(uri): + """ return the parent of the given resource""" + up=urlparse.urlparse(uri) + np=joinfields(split(up[2],"/")[:-1],"/") + return urlparse.urlunparse((up[0],up[1],np,up[3],up[4],up[5])) + +### XML utilities + +def make_xmlresponse(result): + """ construct a response from a dict of uri:error_code elements """ + doc = Document(None) + ms=doc.createElement("D:multistatus") + ms.setAttribute("xmlns:D","DAV:") + doc.appendChild(ms) + + for el,ec in result.items(): + re=doc.createElement("D:response") + hr=doc.createElement("D:href") + st=doc.createElement("D:status") + huri=doc.createTextNode(quote_uri(el)) + t=doc.createTextNode(gen_estring(ec)) + st.appendChild(t) + hr.appendChild(huri) + re.appendChild(hr) + re.appendChild(st) + ms.appendChild(re) + + sfile=StringIO() + ext.PrettyPrint(doc,stream=sfile) + s=sfile.getvalue() + sfile.close() + return s + diff --git a/addons/base_calendar/__init__.py b/addons/base_calendar/__init__.py index c127cc7c124..017a1748449 100644 --- a/addons/base_calendar/__init__.py +++ b/addons/base_calendar/__init__.py @@ -21,5 +21,6 @@ import base_calendar import wizard - +import base_calendar_node +import webdav_server # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/base_calendar/base_calendar.py b/addons/base_calendar/base_calendar.py index da335fda91c..6cb7e5324fd 100644 --- a/addons/base_calendar/base_calendar.py +++ b/addons/base_calendar/base_calendar.py @@ -25,10 +25,13 @@ from dateutil.rrule import * from osv import osv, fields from tools.translate import _ import base64 +import math import pooler +import pytz import re import tools import vobject +import time # O-1 Optional and can come only once # O-n Optional and can come more than once @@ -52,11 +55,11 @@ def uid2openobjectid(cr, uidval, oomodel, rdate): r_id = cr.fetchone() if r_id: return (id, r_id[0]) - cr.execute(qry) + cr.execute(qry) ids = map(lambda x: str(x[0]), cr.fetchall()) if id in ids: return (id, None) - return False + return (False, None) def openobjectid2uid(cr, uidval, oomodel): value = 'OpenObject-%s_%s@%s' % (oomodel, uidval, cr.dbname) @@ -68,7 +71,10 @@ def get_attribute_mapping(cr, uid, calname, context={}): pool = pooler.get_pool(cr.dbname) field_obj = pool.get('basic.calendar.fields') type_obj = pool.get('basic.calendar.lines') - type_id = type_obj.search(cr, uid, [('object_id.model', '=', context.get('model'))]) + domain = [('object_id.model', '=', context.get('model'))] + if context.get('calendar_id'): + domain.append(('calendar_id', '=', context.get('calendar_id'))) + type_id = type_obj.search(cr, uid, domain) fids = field_obj.search(cr, uid, [('type_id', '=', type_id[0])]) res = {} for field in field_obj.browse(cr, uid, fids): @@ -191,6 +197,8 @@ class CalDAV(object): self.ical_set(cal_data.name.lower(), vals, 'value') continue if cal_data.name.lower() in self.__attribute__: + if cal_data.params.get('X-VOBJ-ORIGINAL-TZID'): + self.ical_set('vtimezone', cal_data.params.get('X-VOBJ-ORIGINAL-TZID'), 'value') self.ical_set(cal_data.name.lower(), cal_data.value, 'value') vals = map_data(cr, uid, self) return vals @@ -199,6 +207,7 @@ class CalDAV(object): if not datas: return for data in datas: + tzval = None vevent = ical.add(name) for field in self.__attribute__.keys(): map_field = self.ical_get(field, 'field') @@ -236,6 +245,11 @@ class CalDAV(object): alarm_obj = self.pool.get('basic.calendar.alarm') vevent = alarm_obj.export_cal(cr, uid, model, \ data[map_field][0], vevent, context=ctx) + elif field == 'vtimezone' and data[map_field]: + tzval = data[map_field] + tz_obj = self.pool.get('basic.calendar.timezone') + ical = tz_obj.export_cal(cr, uid, None, \ + data[map_field], ical, context=context) elif data[map_field]: if map_type in ("char", "text"): vevent.add(field).value = tools.ustr(data[map_field]) @@ -243,7 +257,10 @@ class CalDAV(object): if field in ('exdate'): vevent.add(field).value = [parser.parse(data[map_field])] else: - vevent.add(field).value = parser.parse(data[map_field]) + dtfield = vevent.add(field) + dtfield.value = parser.parse(data[map_field]) + if tzval: + dtfield.params['TZID'] = [tzval.title()] elif map_type == "timedelta": vevent.add(field).value = timedelta(hours=data[map_field]) elif map_type == "many2one": @@ -295,8 +312,8 @@ class CalDAV(object): ical_data = base64.decodestring(content) self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, context) parsedCal = vobject.readOne(ical_data) - att_data = [] res = [] + vals = {} for child in parsedCal.getChildren(): if child.name.lower() in ('vevent', 'vtodo'): vals = self.parse_ics(cr, uid, child, context=context) @@ -307,7 +324,6 @@ class CalDAV(object): self.ical_reset('value') return res - class Calendar(CalDAV, osv.osv): _name = 'basic.calendar' _description = 'Calendar' @@ -329,28 +345,34 @@ class Calendar(CalDAV, osv.osv): _columns = { 'name': fields.char("Name", size=64), 'line_ids': fields.one2many('basic.calendar.lines', 'calendar_id', 'Calendar Lines'), - 'active': fields.boolean('Active'), + 'active': fields.boolean('Active'), + 'create_date': fields.datetime('Created Date'), + 'write_date': fields.datetime('Modifided Date'), } _defaults = { 'active': lambda *a: True, } - def export_cal(self, cr, uid, datas, vobj='vevent', context={}): - cal = self.browse(cr, uid, datas[0]) - ical = vobject.iCalendar() + def export_cal(self, cr, uid, ids, vobj='vevent', context={}): + cal = self.browse(cr, uid, ids[0]) + ical = vobject.iCalendar() for line in cal.line_ids: if line.name in ('valarm', 'attendee'): continue mod_obj = self.pool.get(line.object_id.model) data_ids = mod_obj.search(cr, uid, eval(line.domain), context=context) datas = mod_obj.read(cr, uid, data_ids, context=context) - context.update({'model': line.object_id.model}) + context.update({'model': line.object_id.model, + 'calendar_id': cal.id + }) self.__attribute__ = get_attribute_mapping(cr, uid, line.name, context) self.create_ics(cr, uid, datas, line.name, ical, context=context) return ical.serialize() def import_cal(self, cr, uid, content, data_id=None, context=None): + if not context: + context = {} ical_data = base64.decodestring(content) parsedCal = vobject.readOne(ical_data) if not data_id: @@ -362,7 +384,9 @@ class Calendar(CalDAV, osv.osv): cal_children[line.name] = line.object_id.model for child in parsedCal.getChildren(): if child.name.lower() in cal_children: - context.update({'model': cal_children[child.name.lower()]}) + context.update({'model': cal_children[child.name.lower()], + 'calendar_id': cal.id + }) self.__attribute__ = get_attribute_mapping(cr, uid, child.name.lower(), context=context) val = self.parse_ics(cr, uid, child, cal_children=cal_children, context=context) obj = self.pool.get(cal_children[child.name.lower()]) @@ -371,7 +395,6 @@ class Calendar(CalDAV, osv.osv): else: self.check_import(cr, uid, [val], context=context) return {} - Calendar() class basic_calendar_line(osv.osv): @@ -585,15 +608,51 @@ class FreeBusy(CalDAV): } -class Timezone(CalDAV): +class Timezone(CalDAV, osv.osv_memory): + _name = 'basic.calendar.timezone' + _calname = 'vtimezone' + __attribute__ = { - 'tzid': None, # Use: R-1, Type: Text, Specifies the text value that uniquely identifies the "VTIMEZONE" calendar component. + 'tzid': {'field': 'tzid'}, # Use: R-1, Type: Text, Specifies the text value that uniquely identifies the "VTIMEZONE" calendar component. 'last-mod': None, # Use: O-1, Type: DATE-TIME, Specifies the date and time that the information associated with the calendar component was last revised in the calendar store. 'tzurl': None, # Use: O-1, Type: URI, Provides a means for a VTIMEZONE component to point to a network location that can be used to retrieve an up-to-date version of itself. 'standardc': {'tzprop': None}, # Use: R-1, 'daylightc': {'tzprop': None}, # Use: R-1, 'x-prop': None, # Use: O-n, Type: Text, } + + def get_name_offset(self, cr, uid, tzid, context={}): + mytz = pytz.timezone(tzid.title()) + mydt = datetime.now(tz=mytz) + offset = mydt.utcoffset() + val = offset.days * 24 + float(offset.seconds) / 3600 + realoffset = '%02d%02d' % (math.floor(abs(val)), \ + round(abs(val) % 1 + 0.01, 2) * 60) + realoffset = (val < 0 and ('-' + realoffset) or ('+' + realoffset)) + return (mydt.tzname(), realoffset) + + def export_cal(self, cr, uid, model, tzid, ical, context={}): + ctx = context.copy() + ctx.update({'model': model}) + cal_tz = ical.add('vtimezone') + cal_tz.add('TZID').value = tzid.title() + tz_std = cal_tz.add('STANDARD') + tzname, offset = self.get_name_offset(cr, uid, tzid) + tz_std.add("TZOFFSETFROM").value = offset + tz_std.add("TZOFFSETTO").value = offset + tz_std.add("DTSTART").value = datetime.now() # TODO + tz_std.add("TZNAME").value = tzname + return ical + + def import_cal(self, cr, uid, ical_data, context=None): + for child in ical_data.getChildren(): + if child.name.lower() == 'tzid': + tzname = child.value + self.ical_set(child.name.lower(), tzname, 'value') + vals = map_data(cr, uid, self) + return vals + +Timezone() class Alarm(CalDAV, osv.osv_memory): diff --git a/addons/base_calendar/base_calendar_data.xml b/addons/base_calendar/base_calendar_data.xml index 0e7250071c8..b15cd72874e 100644 --- a/addons/base_calendar/base_calendar_data.xml +++ b/addons/base_calendar/base_calendar_data.xml @@ -1,10 +1,7 @@ - + - - OpenERP - diff --git a/addons/base_calendar/base_calendar_node.py b/addons/base_calendar/base_calendar_node.py new file mode 100644 index 00000000000..1f5a2352b5f --- /dev/null +++ b/addons/base_calendar/base_calendar_node.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). +# +# 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 . +# +############################################################################## + +from osv import osv, fields +from tools.translate import _ +import pooler +import tools +import time +import base64 + +class node_calendar(object): + def __init__(self, path, context, calendar): + self.path = path + self.context = context + self.calendar_id = calendar.id + self.mimetype = 'ics' + self.create_date = calendar.create_date + self.write_date = calendar.write_date or calendar.create_date + self.content_length = 0 + self.displayname = calendar.name + + + def get_data(self, cr, uid): + calendar_obj = pooler.get_pool(cr.dbname).get('basic.calendar') + return calendar_obj.export_cal(cr, uid, [self.calendar_id]) + + def get_data_len(self, cr): + return self.content_length + + def set_data(self, cr, uid, data): + calendar_obj = pooler.get_pool(cr.dbname).get('basic.calendar') + return calendar_obj.import_cal(cr, uid, base64.encodestring(data), self.calendar_id) + + def get_etag(self,cr): + """ Get a tag, unique per object + modification. + + see. http://tools.ietf.org/html/rfc2616#section-13.3.3 """ + return self._get_ttag(cr) + ':' + self._get_wtag(cr) + + def _get_wtag(self,cr): + """ Return the modification time as a unique, compact string """ + if self.write_date: + wtime = time.mktime(time.strptime(self.write_date,'%Y-%m-%d %H:%M:%S')) + else: wtime = time.time() + return str(wtime) + + def _get_ttag(self,cr): + return 'calendar-%d' % self.calendar_id + +class Calendar(osv.osv): + _inherit = 'basic.calendar' + + def get_calendar_object(self, cr, uid, uri, context=None): + if not uri: + return None + if len(uri) > 1: + return None + name, file_type = tuple(uri[0].split('.')) + res = self.name_search(cr, uid, name) + if not res: + return None + calendar_id, calendar_name = res[0] + calendar = self.browse(cr, uid, calendar_id) + return node_calendar(uri, context, calendar) +Calendar() diff --git a/addons/base_calendar/cache.py b/addons/base_calendar/cache.py new file mode 100644 index 00000000000..7f03aa6da13 --- /dev/null +++ b/addons/base_calendar/cache.py @@ -0,0 +1,39 @@ +import time +import heapq + +def memoize(maxsize): + """decorator to 'memoize' a function - caching its results""" + def decorating_function(f): + cache = {} # map from key to value + heap = [] # list of keys, in LRU heap + cursize = 0 # because len() is slow + def wrapper(*args): + key = repr(args) + # performance crap + _cache=cache + _heap=heap + _heappop = heapq.heappop + _heappush = heapq.heappush + _time = time.time + _cursize = cursize + _maxsize = maxsize + if not _cache.has_key(key): + if _cursize == _maxsize: + # pop oldest element + (_,oldkey) = _heappop(_heap) + _cache.pop(oldkey) + else: + _cursize += 1 + # insert this element + _cache[key] = f(*args) + _heappush(_heap,(_time(),key)) + wrapper.misses += 1 + else: + wrapper.hits += 1 + return cache[key] + wrapper.__doc__ = f.__doc__ + wrapper.__name__ = f.__name__ + wrapper.hits = wrapper.misses = 0 + return wrapper + return decorating_function + diff --git a/addons/base_calendar/dav_fs.py b/addons/base_calendar/dav_fs.py new file mode 100644 index 00000000000..e44a41a8e07 --- /dev/null +++ b/addons/base_calendar/dav_fs.py @@ -0,0 +1,335 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). +# +# 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 . +# +############################################################################## + +import pooler + +import base64 +import sys +import os +import time +from string import joinfields, split, lower + +from service import security + +import netsvc +import urlparse + +from DAV.constants import COLLECTION, OBJECT +from DAV.errors import * +from DAV.iface import * +import urllib + +from DAV.davcmd import copyone, copytree, moveone, movetree, delone, deltree +from document.nodes import node_res_dir, node_res_obj +from cache import memoize +from tools import misc +CACHE_SIZE=20000 + + +class tinydav_handler(dav_interface): + """ + This class models a Tiny ERP interface for the DAV server + """ + PROPS={'DAV:': dav_interface.PROPS['DAV:'], } + + M_NS={ "DAV:" : dav_interface.M_NS['DAV:'], } + + def __init__(self, parent, verbose=False): + self.db_name = False + self.parent = parent + self.baseuri = parent.baseuri + + def get_propnames(self, uri): + props = self.PROPS + self.parent.log_message('get propnames: %s' % uri) + if uri[-1]=='/':uri=uri[:-1] + cr, uid, pool, dbname, uri2 = self.get_cr(uri) + if not dbname: + cr.close() + return props + node = self.uri2object(cr,uid,pool, uri2) + if node: + props.update(node.get_dav_props(cr)) + cr.close() + return props + + def get_prop(self,uri,ns,propname): + """ return the value of a given property + + uri -- uri of the object to get the property of + ns -- namespace of the property + pname -- name of the property + """ + if self.M_NS.has_key(ns): + return dav_interface.get_prop(self,uri,ns,propname) + + if uri[-1]=='/':uri=uri[:-1] + cr, uid, pool, dbname, uri2 = self.get_cr(uri) + if not dbname: + cr.close() + raise DAV_NotFound + node = self.uri2object(cr,uid,pool, uri2) + if not node: + cr.close() + raise DAV_NotFound + res = node.get_dav_eprop(cr,ns,propname) + cr.close() + return res + + def urijoin(self,*ajoin): + """ Return the base URI of this request, or even join it with the + ajoin path elements + """ + return self.baseuri+ '/'.join(ajoin) + + def uri2local(self, uri): + uparts=urlparse.urlparse(uri) + reluri=uparts[2] + if reluri and reluri[-1]=="/": + reluri=reluri[:-1] + return reluri + + # + # pos: -1 to get the parent of the uri + # + def get_cr(self, uri): + pdb = self.parent.auth_proxy.last_auth + reluri = self.uri2local(uri) + try: + dbname = reluri.split('/')[2] + except: + dbname = False + if not dbname: + return None, None, None, False, None + if not pdb and dbname: + # if dbname was in our uri, we should have authenticated + # against that. + raise Exception("Programming error") + assert pdb == dbname, " %s != %s" %(pdb, dbname) + user, passwd, dbn2, uid = self.parent.auth_proxy.auth_creds[pdb] + db,pool = pooler.get_db_and_pool(dbname) + cr = db.cursor() + uri2 = reluri.split('/')[3:] + return cr, uid, pool, dbname, uri2 + + def uri2object(self, cr, uid, pool, uri): + if not uid: + return None + return pool.get('basic.calendar').get_calendar_object(cr, uid, uri) + + def get_data(self,uri): + self.parent.log_message('GET: %s' % uri) + if uri[-1]=='/':uri=uri[:-1] + cr, uid, pool, dbname, uri2 = self.get_cr(uri) + try: + if not dbname: + raise DAV_Error, 409 + node = self.uri2object(cr,uid,pool, uri2) + if not node: + raise DAV_NotFound(uri2) + try: + datas = node.get_data(cr, uid) + except TypeError,e: + import traceback + self.parent.log_error("GET typeError: %s", str(e)) + self.parent.log_message("Exc: %s",traceback.format_exc()) + raise DAV_Forbidden + except IndexError,e : + self.parent.log_error("GET IndexError: %s", str(e)) + raise DAV_NotFound(uri2) + except Exception,e: + import traceback + self.parent.log_error("GET exception: %s",str(e)) + self.parent.log_message("Exc: %s", traceback.format_exc()) + raise DAV_Error, 409 + return datas + finally: + cr.close() + + @memoize(CACHE_SIZE) + def _get_dav_resourcetype(self,uri): + """ return type of object """ + self.parent.log_message('get RT: %s' % uri) + if uri[-1]=='/':uri=uri[:-1] + cr, uid, pool, dbname, uri2 = self.get_cr(uri) + try: + if not dbname: + return COLLECTION + node = self.uri2object(cr,uid,pool, uri2) + if not node: + raise DAV_NotFound(uri2) + return OBJECT + finally: + cr.close() + + def _get_dav_displayname(self,uri): + self.parent.log_message('get DN: %s' % uri) + if uri[-1]=='/':uri=uri[:-1] + cr, uid, pool, dbname, uri2 = self.get_cr(uri) + if not dbname: + cr.close() + return COLLECTION + node = self.uri2object(cr,uid,pool, uri2) + if not node: + cr.close() + raise DAV_NotFound(uri2) + cr.close() + return node.displayname + + @memoize(CACHE_SIZE) + def _get_dav_getcontentlength(self,uri): + """ return the content length of an object """ + self.parent.log_message('get length: %s' % uri) + if uri[-1]=='/':uri=uri[:-1] + result = 0 + cr, uid, pool, dbname, uri2 = self.get_cr(uri) + if not dbname: + cr.close() + return '0' + node = self.uri2object(cr, uid, pool, uri2) + if not node: + cr.close() + raise DAV_NotFound(uri2) + result = node.content_length or 0 + cr.close() + return str(result) + + @memoize(CACHE_SIZE) + def _get_dav_getetag(self,uri): + """ return the ETag of an object """ + self.parent.log_message('get etag: %s' % uri) + if uri[-1]=='/':uri=uri[:-1] + result = 0 + cr, uid, pool, dbname, uri2 = self.get_cr(uri) + if not dbname: + cr.close() + return '0' + node = self.uri2object(cr, uid, pool, uri2) + if not node: + cr.close() + raise DAV_NotFound(uri2) + result = node.get_etag(cr) + cr.close() + return str(result) + + @memoize(CACHE_SIZE) + def get_lastmodified(self,uri): + """ return the last modified date of the object """ + if uri[-1]=='/':uri=uri[:-1] + today = time.time() + cr, uid, pool, dbname, uri2 = self.get_cr(uri) + try: + if not dbname: + return today + node = self.uri2object(cr,uid,pool, uri2) + if not node: + raise DAV_NotFound(uri2) + if node.write_date: + return time.mktime(time.strptime(node.write_date,'%Y-%m-%d %H:%M:%S')) + else: + return today + finally: + cr.close() + + @memoize(CACHE_SIZE) + def get_creationdate(self,uri): + """ return the last modified date of the object """ + + if uri[-1]=='/':uri=uri[:-1] + cr, uid, pool, dbname, uri2 = self.get_cr(uri) + try: + if not dbname: + raise DAV_Error, 409 + node = self.uri2object(cr,uid,pool, uri2) + if not node: + raise DAV_NotFound(uri2) + if node.create_date: + result = time.strptime(node.create_date,'%Y-%m-%d %H:%M:%S') + else: + result = time.gmtime() + return result + finally: + cr.close() + + @memoize(CACHE_SIZE) + def _get_dav_getcontenttype(self,uri): + self.parent.log_message('get contenttype: %s' % uri) + if uri[-1]=='/':uri=uri[:-1] + cr, uid, pool, dbname, uri2 = self.get_cr(uri) + try: + if not dbname: + return 'httpd/unix-directory' + node = self.uri2object(cr,uid,pool, uri2) + if not node: + raise DAV_NotFound(uri2) + result = node.mimetype + return result + #raise DAV_NotFound, 'Could not find %s' % path + finally: + cr.close() + + + + def put(self, uri, data, content_type=None): + """ put the object into the filesystem """ + self.parent.log_message('Putting %s (%d), %s'%( misc.ustr(uri), len(data), content_type)) + parent='/'.join(uri.split('/')[:-1]) + cr, uid, pool,dbname, uri2 = self.get_cr(uri) + if not dbname: + raise DAV_Forbidden + try: + node = self.uri2object(cr,uid,pool, uri2[:]) + except: + node = False + + if not node: + raise DAV_Forbidden + else: + try: + node.set_data(cr, uid, data) + except Exception,e: + import traceback + self.parent.log_error("Cannot save :%s", str(e)) + self.parent.log_message("Exc: %s",traceback.format_exc()) + raise DAV_Forbidden + + cr.commit() + cr.close() + return 201 + + + + + def exists(self,uri): + """ test if a resource exists """ + result = False + cr, uid, pool,dbname, uri2 = self.get_cr(uri) + if not dbname: + cr.close() + return True + try: + node = self.uri2object(cr,uid,pool, uri2) + if node: + result = True + except: + pass + cr.close() + return result diff --git a/addons/base_calendar/webdav_server.py b/addons/base_calendar/webdav_server.py new file mode 100644 index 00000000000..9fd6d8bf009 --- /dev/null +++ b/addons/base_calendar/webdav_server.py @@ -0,0 +1,79 @@ +# -*- encoding: utf-8 -*- + +# +# Copyright P. Christeas 2008,2009 +# +# +# 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 contract 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +############################################################################### + + +import netsvc +from dav_fs import tinydav_handler +from tools.config import config +from DAV.WebDAVServer import DAVRequestHandler +from service.websrv_lib import HTTPDir,FixSendError + +class DAVHandler(FixSendError,DAVRequestHandler): + verbose = False + + def get_userinfo(self,user,pw): + return False + + def _log(self, message): + netsvc.Logger().notifyChannel("webdav",netsvc.LOG_DEBUG,message) + + def handle(self): + pass + + def finish(self): + pass + + def setup(self): + davpath = '/calendar/' + self.baseuri = "http://%s:%d%s"% (self.server.server_name,self.server.server_port,davpath) + self.IFACE_CLASS = tinydav_handler(self) + pass + + def log_message(self, format, *args): + netsvc.Logger().notifyChannel('webdav',netsvc.LOG_DEBUG_RPC,format % args) + + def log_error(self, format, *args): + netsvc.Logger().notifyChannel('webdav',netsvc.LOG_WARNING,format % args) + + +try: + from service.http_server import reg_http_service,OpenERPAuthProvider + davpath = '/calendar/' + handler = DAVHandler + handler.verbose = config.get_misc('webdav','verbose',True) + handler.debug = config.get_misc('webdav','debug',True) + reg_http_service(HTTPDir(davpath,DAVHandler,OpenERPAuthProvider())) + netsvc.Logger().notifyChannel('webdav',netsvc.LOG_INFO,"WebDAV service registered at path: %s/ "% davpath) +except Exception, e: + logger = netsvc.Logger() + logger.notifyChannel('webdav', netsvc.LOG_ERROR, 'Cannot launch webdav: %s' % e) + +#eof + + + diff --git a/addons/base_contact/i18n/nl.po b/addons/base_contact/i18n/nl.po index 31eb515e773..21dacd2804b 100644 --- a/addons/base_contact/i18n/nl.po +++ b/addons/base_contact/i18n/nl.po @@ -7,13 +7,13 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.0\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2009-08-28 16:01+0000\n" -"PO-Revision-Date: 2009-12-11 22:28+0000\n" -"Last-Translator: Pieter J. Kersten (EduSense BV) \n" +"PO-Revision-Date: 2010-02-01 15:47+0000\n" +"Last-Translator: Jan Verlaan (Veritos) \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-12-16 05:08+0000\n" +"X-Launchpad-Export-Date: 2010-02-02 04:48+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: base_contact @@ -36,7 +36,7 @@ msgstr "res.partner.contact" msgid "" "The Object name must start with x_ and not contain any special character !" msgstr "" -"De objectnaam moet beginnen met x_ en mag geen speciale karakters bevatten !" +"De objectnaam moet beginnen met x_ en mag geen speciale tekens bevatten !" #. module: base_contact #: field:res.partner.job,function_id:0 diff --git a/addons/base_report_creator/i18n/zh_CN.po b/addons/base_report_creator/i18n/zh_CN.po index 69e7da8c2f5..cfd7bb92238 100644 --- a/addons/base_report_creator/i18n/zh_CN.po +++ b/addons/base_report_creator/i18n/zh_CN.po @@ -7,13 +7,13 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.0\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2009-08-28 16:01+0000\n" -"PO-Revision-Date: 2009-12-01 15:37+0000\n" -"Last-Translator: Wei \"oldrev\" Li \n" +"PO-Revision-Date: 2010-02-01 23:39+0000\n" +"Last-Translator: mrshelly \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-12-16 05:21+0000\n" +"X-Launchpad-Export-Date: 2010-02-02 04:48+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: base_report_creator @@ -22,7 +22,7 @@ msgid "" "Provide an expression for the field based on which you want to filter the " "records.\n" " e.g. res_partner.id=3" -msgstr "" +msgstr "可以使用基于字段的Python条件表达式做为过滤条件.(如 res_partner.id=3)" #. module: base_report_creator #: field:base_report_creator.report,view_graph_type:0 @@ -406,6 +406,8 @@ msgid "" "After installing the module, it adds a menu to define custom report in\n" "the \"Dashboard\" menu.\n" msgstr "" +"本模块可以让您使用SQL查询器新建很灵活的对象统计报表.\n" +"安装此模块后, \"Dashboard(系统面板)\" 上会添加对应的菜单入口.\n" #. module: base_report_creator #: selection:base_report_creator.report.fields,calendar_mode:0 diff --git a/addons/caldav/caldav.py b/addons/caldav/caldav.py index 5741f645b1e..be51f321f9d 100644 --- a/addons/caldav/caldav.py +++ b/addons/caldav/caldav.py @@ -19,15 +19,16 @@ # ############################################################################## +from base_calendar import base_calendar from datetime import datetime, timedelta from datetime import datetime, timedelta from dateutil import parser from osv import fields, osv -from base_calendar import base_calendar from service import web_services from tools.translate import _ import base64 import pooler +import pytz import re import time @@ -423,6 +424,9 @@ class calendar_event(osv.osv): _name = "calendar.event" _description = "Calendar Event" __attribute__ = {} + + def _tz_get(self,cr,uid, context={}): + return [(x.lower(), x) for x in pytz.all_timezones] def onchange_rrule_type(self, cr, uid, ids, rtype, *args, **argv): if rtype == 'none' or not rtype: @@ -483,7 +487,9 @@ rule or repeating pattern for anexception to a recurrence set"), 'caldav_alarm_id': fields.many2one('calendar.alarm', 'Alarm'), 'recurrent_uid': fields.integer('Recurrent ID'), 'recurrent_id': fields.datetime('Recurrent ID date'), - } + 'vtimezone': fields.selection(_tz_get, 'Timezone', size=64), + 'user_id': fields.many2one('res.users', 'Responsible'), + } _defaults = { 'class': lambda *a: 'public', diff --git a/addons/caldav/caldav_data.xml b/addons/caldav/caldav_data.xml index 295b05ba540..c8b20ac134b 100644 --- a/addons/caldav/caldav_data.xml +++ b/addons/caldav/caldav_data.xml @@ -129,381 +129,5 @@ - - - vevent - - - - - - vtodo - - - - - - valarm - - - - - - attendee - - - - - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - - - - - - field - - -
diff --git a/addons/caldav/caldav_view.xml b/addons/caldav/caldav_view.xml index 559c45485cc..4d10b9529f5 100644 --- a/addons/caldav/caldav_view.xml +++ b/addons/caldav/caldav_view.xml @@ -65,6 +65,8 @@ + + @@ -114,7 +116,7 @@
- + calendar.attendee.tree calendar.attendee tree @@ -126,32 +128,9 @@ - + - - My Invitations - ir.actions.act_window - calendar.attendee - form - tree,form - - [('user_id', '=', uid)] - {'default_sent_by_uid': uid} - - - - - tree - - - - - - form - - - res.alarm.form @@ -251,7 +230,7 @@
- + Custom Rule ir.actions.act_window calendar.custom.rrule @@ -260,9 +239,14 @@ new - + + + + + + diff --git a/addons/crm/crm.py b/addons/crm/crm.py index ed4735a55e8..d9d4c6e6c9d 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -580,7 +580,7 @@ class crm_case(osv.osv): def __history(self, cr, uid, cases, keyword, history=False, email=False, details=None, context={}): - model_obj = self.pool.get('ir.model') + model_obj = self.pool.get('ir.model') for case in cases: model_ids = model_obj.search(cr, uid, [('model','=',case._name)]) data = { @@ -820,14 +820,7 @@ class crm_case_history(osv.osv): _name = "crm.case.history" _description = "Case history" _order = "id desc" - _inherits = {'crm.case.log':"log_id"} - - def create(self, cr, user, vals, context=None): - if vals.has_key('res_id') and vals['res_id']: - case_obj = self.pool.get(vals['model_id']) - cases = case_obj.browse(cr, user, [vals['res_id']]) - case_obj._action(cr, user, cases, '') - return super(crm_case_history, self).create(cr, user, vals, context) + _inherits = {'crm.case.log':"log_id"} def _note_get(self, cursor, user, ids, name, arg, context=None): res = {} diff --git a/addons/crm/crm_claim.py b/addons/crm/crm_claim.py index 5e83ce1c367..14921372fad 100644 --- a/addons/crm/crm_claim.py +++ b/addons/crm/crm_claim.py @@ -19,16 +19,9 @@ # ############################################################################## -import tools from osv import fields, osv -import os -import pooler -import netsvc -from tools.translate import _ import crm - - class crm_claim(osv.osv): _name = "crm.claim" _description = "Claim Cases" @@ -59,6 +52,7 @@ class crm_claim(osv.osv): _defaults = { 'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0], } + def onchange_categ_id(self, cr, uid, ids, categ, context={}): if not categ: return {'value':{}} diff --git a/addons/crm/crm_claims_view.xml b/addons/crm/crm_claims_view.xml index 17dd87caa50..5f7e9b07fdc 100644 --- a/addons/crm/crm_claims_view.xml +++ b/addons/crm/crm_claims_view.xml @@ -8,24 +8,24 @@ Claim Categories crm.case.categ form - + [('object_id.model', '=', 'crm.claim')] - {'object_id':'crm.claim'} + {'object_id':'crm.claim'} # ------------------------------------------------------ # Stages - # ------------------------------------------------------ + # ------------------------------------------------------ Claim Stages crm.case.stage form [('object_id.model', '=', 'crm.claim')] - {'object_id':'crm.claim'} + {'object_id':'crm.claim'} - + CRM - Claims Tree @@ -169,7 +169,7 @@ - + CRM - Claims Search crm.claim @@ -198,12 +198,12 @@ - + - + /> + diff --git a/addons/crm/crm_fund_view.xml b/addons/crm/crm_fund_view.xml index 4fec4afb976..96b0ac51ad9 100644 --- a/addons/crm/crm_fund_view.xml +++ b/addons/crm/crm_fund_view.xml @@ -9,9 +9,9 @@ Fundraising Categories crm.case.categ form - - [('object_id.model', '=', 'crm.fundraising')] - {'object_id':'crm.fundraising'} + + [('object_id.model', '=', 'crm.fundraising')] + {'object_id':'crm.fundraising'} # ------------------------------------------------------ @@ -23,10 +23,10 @@ form [('object_id.model', '=', 'crm.fundraising')] - {'object_id':'crm.fundraising'} + {'object_id':'crm.fundraising'} - + # ------------------------------------------------------ # Fund Raising # ------------------------------------------------------ @@ -194,12 +194,12 @@ - + - + /> + diff --git a/addons/crm/crm_fundraising.py b/addons/crm/crm_fundraising.py index 046065e61f2..5085f01333f 100644 --- a/addons/crm/crm_fundraising.py +++ b/addons/crm/crm_fundraising.py @@ -19,23 +19,9 @@ # ############################################################################## -import time -import re -import os - -import mx.DateTime -import base64 - -from tools.translate import _ - -import tools from osv import fields,osv,orm -from osv.orm import except_orm - import crm - - class crm_fundraising(osv.osv): _name = "crm.fundraising" _description = "Fund Raising Cases" @@ -67,11 +53,11 @@ class crm_fundraising(osv.osv): _defaults = { 'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0], } + def onchange_categ_id(self, cr, uid, ids, categ, context={}): if not categ: return {'value':{}} cat = self.pool.get('crm.case.categ').browse(cr, uid, categ, context).probability return {'value':{'probability':cat}} - crm_fundraising() diff --git a/addons/crm/crm_helpdesk.py b/addons/crm/crm_helpdesk.py index ca4bf47006b..13bc13d214c 100644 --- a/addons/crm/crm_helpdesk.py +++ b/addons/crm/crm_helpdesk.py @@ -19,23 +19,9 @@ # ############################################################################## -import time -import re -import os - -import mx.DateTime -import base64 - -from tools.translate import _ - -import tools from osv import fields,osv,orm -from osv.orm import except_orm - import crm - - class crm_helpdesk(osv.osv): _name = "crm.helpdesk" _description = "Helpdesk Cases" @@ -57,6 +43,7 @@ class crm_helpdesk(osv.osv): 'categ_id': fields.many2one('crm.case.categ', 'Category', domain="[('section_id','=',section_id),('object_id.model', '=', 'crm.helpdesk')]"), 'duration': fields.float('Duration'), } + def onchange_categ_id(self, cr, uid, ids, categ, context={}): if not categ: return {'value':{}} diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 9c6ae7bffa7..f346bcf88f7 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -19,22 +19,9 @@ # ############################################################################## -import time -import re -import os - -import mx.DateTime -import base64 - -from tools.translate import _ - -import tools from osv import fields,osv,orm -from osv.orm import except_orm - import crm - class crm_opportunity(osv.osv): _name = "crm.opportunity" _description = "Opportunity Cases" @@ -65,9 +52,9 @@ class crm_lead(osv.osv): 'som': fields.many2one('res.partner.som', 'State of Mind', help="The minds states allow to define a value scale which represents" \ "the partner mentality in relation to our services.The scale has" \ "to be created with a factor for each level from 0 (Very dissatisfied) to 10 (Extremely satisfied)."), - 'opportunity_id': fields.many2one ('crm.opportunity', 'Opportunity'), } + def onchange_categ_id(self, cr, uid, ids, categ, context={}): if not categ: return {'value':{}} diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml index 975bb764ce7..2c5024ebd0f 100644 --- a/addons/crm/crm_lead_view.xml +++ b/addons/crm/crm_lead_view.xml @@ -2,23 +2,23 @@ - - - # ------------------------------------------------------ + + + # ------------------------------------------------------ # Stage - # ------------------------------------------------------ + # ------------------------------------------------------ Lead Stages crm.case.stage form [('object_id.model', '=', 'crm.lead')] - {'object_id':'crm.lead'} + {'object_id':'crm.lead'} # ------------------------------------------------------ # Leads - # ------------------------------------------------------ + # ------------------------------------------------------ CRM - Leads Form @@ -29,9 +29,9 @@ - +