diff --git a/MANIFEST.in b/MANIFEST.in index 70715f18bc2..c864492136d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,12 +1,13 @@ +graft debian +graft doc +graft install +graft openerp +graft tests +graft win32 include README include LICENSE include MANIFEST.in -include setup.nsi -include setup.cfg -include setup_rpm.sh -recursive-include win32 *.py *.bat -recursive-include openerp *css *csv *html *png *po *pot *rml *rng *sql *sxw *xml *xsl *yml -graft install -graft debian -graft doc +include gunicorn.conf.py +include openerp-server +include setup* global-exclude *pyc *~ # Exclude possible garbage from previous graft. diff --git a/debian/openerp.postinst b/debian/openerp.postinst index 362741bb7e9..f8f653397fe 100644 --- a/debian/openerp.postinst +++ b/debian/openerp.postinst @@ -12,9 +12,9 @@ case "${1}" in chown openerp:openerp /etc/openerp/openerp-server.conf chmod 0640 /etc/openerp/openerp-server.conf # Creating log file - touch /var/log/openerp.log - chown openerp:openerp /var/log/openerp.log - chmod 0640 /var/log/openerp.log + touch /var/log/openerp-server.log + chown openerp:openerp /var/log/openerp-server.log + chmod 0640 /var/log/openerp-server.log # Creating local storage directory mkdir -p /var/lib/openerp/filestore chown openerp:openerp -R /var/lib/openerp diff --git a/debian/po/es_CL.po b/debian/po/es_CL.po index 454fa9705eb..9da94b6ce29 100644 --- a/debian/po/es_CL.po +++ b/debian/po/es_CL.po @@ -8,20 +8,20 @@ msgstr "" "Project-Id-Version: openobject-server\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-08-24 22:41+0300\n" -"PO-Revision-Date: 2011-01-19 14:33+0000\n" -"Last-Translator: Juan Pizarro \n" +"PO-Revision-Date: 2011-10-03 16:05+0000\n" +"Last-Translator: doingit.cl \n" "Language-Team: Spanish (Chile) \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-09-01 04:46+0000\n" -"X-Generator: Launchpad (build 13827)\n" +"X-Launchpad-Export-Date: 2011-10-04 05:01+0000\n" +"X-Generator: Launchpad (build 14071)\n" #. Type: string #. Description #: ../openerp-server.templates:1001 msgid "Dedicated system account for the Open ERP server:" -msgstr "" +msgstr "Cuenta del sistema dedicada para el servidor OpenERP" #. Type: string #. Description @@ -31,9 +31,12 @@ msgid "" "the system's security is not compromised by running it with superuser " "privileges." msgstr "" +"El servidor OpenERP debe utilizar una cuenta dedicada para su " +"funcionamiento, de tal modo que la seguridad del sistema no se vea " +"comprometida por su utilización con privilegios de administración." #. Type: string #. Description #: ../openerp-server.templates:1001 msgid "Please choose that account's username." -msgstr "" +msgstr "Elija un Nombre de Usuario para la cuenta" diff --git a/openerp/addons/base/__openerp__.py b/openerp/addons/base/__openerp__.py index e436a0a6005..f7981133142 100644 --- a/openerp/addons/base/__openerp__.py +++ b/openerp/addons/base/__openerp__.py @@ -24,8 +24,7 @@ { 'name': 'Base', 'version': '1.3', - 'category': 'Generic Modules/Base', - 'complexity': "easy", + 'category': 'System', 'description': """The kernel of OpenERP, needed for all installation.""", 'author': 'OpenERP SA', 'maintainer': 'OpenERP SA', diff --git a/openerp/addons/base/base_data.xml b/openerp/addons/base/base_data.xml index feaac206723..94a3780653f 100644 --- a/openerp/addons/base/base_data.xml +++ b/openerp/addons/base/base_data.xml @@ -1,6 +1,10 @@ + + web.base.url + http://localhost:8069 + ir.ui.menu.tree ir.ui.menu diff --git a/openerp/addons/base/i18n/es_CL.po b/openerp/addons/base/i18n/es_CL.po index 9a5dd8a395a..812eaf625e3 100644 --- a/openerp/addons/base/i18n/es_CL.po +++ b/openerp/addons/base/i18n/es_CL.po @@ -7,13 +7,13 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.4\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2011-01-11 11:14+0000\n" -"PO-Revision-Date: 2011-07-26 01:09+0000\n" -"Last-Translator: Juano \n" +"PO-Revision-Date: 2011-10-03 16:01+0000\n" +"Last-Translator: Francisco Reyes Acuña \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: 2011-10-01 05:07+0000\n" +"X-Launchpad-Export-Date: 2011-10-04 05:01+0000\n" "X-Generator: Launchpad (build 14071)\n" #. module: base @@ -5726,7 +5726,7 @@ msgstr "Traducciones" #. module: base #: field:ir.sequence,padding:0 msgid "Number Padding" -msgstr "" +msgstr "Número(s) de relleno" #. module: base #: view:ir.actions.report.xml:0 diff --git a/openerp/addons/base/i18n/sl.po b/openerp/addons/base/i18n/sl.po index dbb290a3b5a..ee292629ca7 100644 --- a/openerp/addons/base/i18n/sl.po +++ b/openerp/addons/base/i18n/sl.po @@ -7,14 +7,14 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.4\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2011-01-11 11:14+0000\n" -"PO-Revision-Date: 2011-09-30 21:29+0000\n" -"Last-Translator: Antony Lesuisse (OpenERP) \n" +"PO-Revision-Date: 2011-10-04 13:54+0000\n" +"Last-Translator: Mustufa Rangwala (Open ERP) \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: 2011-10-01 05:05+0000\n" -"X-Generator: Launchpad (build 14071)\n" +"X-Launchpad-Export-Date: 2011-10-05 04:44+0000\n" +"X-Generator: Launchpad (build 14085)\n" #. module: base #: view:ir.filters:0 @@ -718,7 +718,7 @@ msgstr "Oman" #: model:ir.actions.act_window,name:base.action_payterm_form #: model:ir.model,name:base.model_res_payterm msgid "Payment term" -msgstr "" +msgstr "Plačilni pogoj" #. module: base #: model:res.country,name:base.nu @@ -7966,7 +7966,7 @@ msgstr "Pošlji E-pošto" #: field:res.config.users,menu_id:0 #: field:res.users,menu_id:0 msgid "Menu Action" -msgstr "Menuji" +msgstr "Dejanja menija" #. module: base #: help:ir.model.fields,selection:0 @@ -7975,6 +7975,8 @@ msgid "" "defining a list of (key, label) pairs. For example: " "[('blue','Blue'),('yellow','Yellow')]" msgstr "" +"Seznam možnosti za izbiro polja, določen kot Python izraz, ki opredeljuje " +"seznam (key, label) parov. Na primer: [('blue','Blue'),('yellow','Yellow')]" #. module: base #: selection:base.language.export,state:0 @@ -7987,6 +7989,8 @@ msgid "" "Indicates whether this object model lives in memory only, i.e. is not " "persisted (osv.osv_memory)" msgstr "" +"Označuje, ali ta model predmeta živi v spominu samo, če ne ostaja tam " +"(osv.osv_memory)" #. module: base #: field:res.partner,child_ids:0 @@ -7999,12 +8003,12 @@ msgstr "Sklic partnerja" #: model:ir.ui.menu,name:base.menu_procurement_management_supplier_name #: view:res.partner:0 msgid "Suppliers" -msgstr "" +msgstr "Dobavitelji" #. module: base #: view:publisher_warranty.contract.wizard:0 msgid "Register" -msgstr "" +msgstr "Registracija" #. module: base #: field:res.request,ref_doc2:0 @@ -8014,7 +8018,7 @@ msgstr "2.sklic dokument" #. module: base #: field:res.request,ref_doc1:0 msgid "Document Ref 1" -msgstr "1.sklic dokumenta" +msgstr "" #. module: base #: model:res.country,name:base.ga @@ -8046,7 +8050,7 @@ msgstr "Številka konta" #. module: base #: view:res.lang:0 msgid "1. %c ==> Fri Dec 5 18:25:20 2008" -msgstr "" +msgstr "Copy text \t 1. %c ==> Pet 5.Dec 2008 5 18:25:20" #. module: base #: model:res.country,name:base.nc @@ -8065,6 +8069,9 @@ msgid "" "loading a new language it becomes available as default interface language " "for users and partners." msgstr "" +"Ta čarovnik vam pomaga dodati nov jezik v OpenERP sistem. Po nalaganju " +"novega jezika, postane ta jezik privzeti jezik vmesnika za uporabnike in " +"partnerje." #. module: base #: field:ir.actions.server,subject:0 @@ -8082,12 +8089,12 @@ msgstr "Od" #. module: base #: view:res.users:0 msgid "Preferences" -msgstr "" +msgstr "Nastavitve" #. module: base #: model:res.partner.category,name:base.res_partner_category_consumers0 msgid "Consumers" -msgstr "" +msgstr "Stranke" #. module: base #: view:res.config:0 @@ -8101,6 +8108,7 @@ msgid "" "Name of the method to be called on the object when this scheduler is " "executed." msgstr "" +"Ime metode, ki se jo kliče na predmet, ko je izvršen razporejevalnik." #. module: base #: code:addons/base/ir/ir_model.py:219 @@ -8108,12 +8116,12 @@ msgstr "" msgid "" "The Selection Options expression is must be in the [('key','Label'), ...] " "format!" -msgstr "" +msgstr "Izbor možnosti izraza, ki mora biti v obliki [('key','Label'), ...]!" #. module: base #: view:ir.actions.report.xml:0 msgid "Miscellaneous" -msgstr "" +msgstr "Razno" #. module: base #: model:res.country,name:base.cn @@ -8127,6 +8135,8 @@ msgid "" "--\n" "%(name)s %(email)s\n" msgstr "" +"--\n" +"%(name)s %(email)s\n" #. module: base #: model:res.country,name:base.eh @@ -8136,7 +8146,7 @@ msgstr "Zahodna Sahara" #. module: base #: model:ir.model,name:base.model_workflow msgid "workflow" -msgstr "potek dela" +msgstr "delovni proces" #. module: base #: model:ir.actions.act_window,help:base.action_res_company_form @@ -8144,6 +8154,8 @@ msgid "" "Create and manage the companies that will be managed by OpenERP from here. " "Shops or subsidiaries can be created and maintained from here." msgstr "" +"Ustvarjanje in upravljanje podjetij, ki bo vodil OpenERP od tukaj. Trgovine " +"ali hčerinskih družbe je mogoče ustvariti in vzdrževati od tukaj." #. module: base #: model:res.country,name:base.id @@ -8157,6 +8169,9 @@ msgid "" "you can then add translations manually or perform a complete export (as a " "template for a new language example)." msgstr "" +"Ta čarovnik bo samodejno zaznal nove izraze za prevod aplikacije, tako da " +"lahko nato dodate prevode ročno ali izvedete popoln izvoz (kot predlogo za " +"nov primer jezika)." #. module: base #: help:multi_company.default,expression:0 @@ -8164,6 +8179,8 @@ msgid "" "Expression, must be True to match\n" "use context.get or user (browse)" msgstr "" +"Izraz mora biti True za ujemanje\n" +"uporabite context.get ali user (prebrskaj)" #. module: base #: model:res.country,name:base.bg @@ -8173,7 +8190,7 @@ msgstr "Bolgarija" #. module: base #: view:publisher_warranty.contract.wizard:0 msgid "Publisher warranty contract successfully registered!" -msgstr "" +msgstr "Pogodba založniške garancije je bila uspešno registrirana!" #. module: base #: model:res.country,name:base.ao @@ -8198,23 +8215,23 @@ msgstr "Valuta" #. module: base #: field:res.partner.canal,name:0 msgid "Channel Name" -msgstr "Naziv kanala" +msgstr "Ime kanala" #. module: base #: view:res.lang:0 msgid "5. %y, %Y ==> 08, 2008" -msgstr "" +msgstr "5. %y, %Y ==> 08, 2008" #. module: base #: model:res.partner.title,shortcut:base.res_partner_title_ltd msgid "ltd" -msgstr "" +msgstr "d.o.o." #. module: base #: field:ir.values,res_id:0 #: field:res.log,res_id:0 msgid "Object ID" -msgstr "" +msgstr "ID predmeta" #. module: base #: view:res.company:0 @@ -8229,7 +8246,7 @@ msgstr "Skrbništvo" #. module: base #: view:base.module.update:0 msgid "Click on Update below to start the process..." -msgstr "" +msgstr "Kliknite na Posodobi za začetek procesa..." #. module: base #: model:res.country,name:base.ir @@ -8240,7 +8257,7 @@ msgstr "Iran" #: model:ir.actions.act_window,name:base.res_widget_user_act_window #: model:ir.ui.menu,name:base.menu_res_widget_user_act_window msgid "Widgets per User" -msgstr "" +msgstr "Gradiniki na uporabnika" #. module: base #: selection:base.language.install,lang:0 @@ -8257,23 +8274,23 @@ msgstr "neznano" #. module: base #: field:res.currency,symbol:0 msgid "Symbol" -msgstr "" +msgstr "Simbol" #. module: base #: help:res.config.users,login:0 #: help:res.users,login:0 msgid "Used to log into the system" -msgstr "" +msgstr "Uporablja se prijavo v sistem" #. module: base #: view:base.update.translations:0 msgid "Synchronize Translation" -msgstr "" +msgstr "Sinhronizacija prevoda" #. module: base #: field:ir.ui.view_sc,res_id:0 msgid "Resource Ref." -msgstr "Sklic resursa" +msgstr "Sklic sredstva" #. module: base #: model:res.country,name:base.ki @@ -8288,7 +8305,7 @@ msgstr "Irak" #. module: base #: model:ir.ui.menu,name:base.menu_association msgid "Association" -msgstr "" +msgstr "Povezava" #. module: base #: model:res.country,name:base.cl @@ -8300,7 +8317,7 @@ msgstr "Čile" #: model:ir.ui.menu,name:base.menu_config_address_book #: model:ir.ui.menu,name:base.menu_procurement_management_supplier msgid "Address Book" -msgstr "" +msgstr "Imenik" #. module: base #: model:ir.model,name:base.model_ir_sequence_type @@ -8315,23 +8332,23 @@ msgstr "CSV datoteka" #. module: base #: field:res.company,account_no:0 msgid "Account No." -msgstr "" +msgstr "Številka konta" #. module: base #: code:addons/base/res/res_lang.py:157 #, python-format msgid "Base Language 'en_US' can not be deleted !" -msgstr "" +msgstr "Osnovnega jezika 'en_US' ni mogoče izbrisati!" #. module: base #: selection:ir.model,state:0 msgid "Base Object" -msgstr "Temeljni objekt" +msgstr "Temeljni predmet" #. module: base #: report:ir.module.reference.graph:0 msgid "Dependencies :" -msgstr "" +msgstr "Odvisnosti:" #. module: base #: field:ir.model.fields,field_description:0 @@ -8360,11 +8377,13 @@ msgid "" "Operation prohibited by access rules, or performed on an already deleted " "document (Operation: %s, Document type: %s)." msgstr "" +"Operacijo prepovedujejo pravila dostopa ali je izvedena na že zbrisanem " +"dokumentu (Operacija: %s, vrsta dokumenta: %s)." #. module: base #: model:res.country,name:base.zr msgid "Zaire" -msgstr "" +msgstr "Zaire" #. module: base #: field:ir.model.data,res_id:0 @@ -8372,7 +8391,7 @@ msgstr "" #: field:workflow.instance,res_id:0 #: field:workflow.triggers,res_id:0 msgid "Resource ID" -msgstr "Resurs ID" +msgstr "ID vira" #. module: base #: view:ir.cron:0 @@ -8383,12 +8402,12 @@ msgstr "Informacije" #. module: base #: view:res.widget.user:0 msgid "User Widgets" -msgstr "" +msgstr "Uporabniški gradniki" #. module: base #: view:base.module.update:0 msgid "Update Module List" -msgstr "" +msgstr "Posodobi seznam modulov" #. module: base #: selection:res.partner.address,type:0 @@ -8422,34 +8441,34 @@ msgstr "Samoosvežitev" #: code:addons/base/ir/ir_model.py:62 #, python-format msgid "The osv_memory field can only be compared with = and != operator." -msgstr "" +msgstr "Polje osv_memory lahko primerjate samo z operatorjem = in !=." #. module: base #: selection:ir.ui.view,type:0 msgid "Diagram" -msgstr "" +msgstr "Diagram" #. module: base #: help:multi_company.default,name:0 msgid "Name it to easily find a record" -msgstr "" +msgstr "Poimenujte, da boste lažje našli zapis" #. module: base #: model:ir.actions.act_window,name:base.grant_menu_access #: model:ir.ui.menu,name:base.menu_grant_menu_access msgid "Menu Items" -msgstr "" +msgstr "Postavke menija" #. module: base #: constraint:ir.rule:0 msgid "Rules are not supported for osv_memory objects !" -msgstr "" +msgstr "Pravila niso podprta za predmete osv_memory!" #. module: base #: model:ir.ui.menu,name:base.menu_event_association #: model:ir.ui.menu,name:base.menu_event_main msgid "Events Organisation" -msgstr "" +msgstr "Organizacija dogodkov" #. module: base #: model:ir.actions.act_window,name:base.ir_sequence_actions @@ -8458,7 +8477,7 @@ msgstr "" #: model:ir.ui.menu,name:base.next_id_6 #: view:workflow.activity:0 msgid "Actions" -msgstr "Akcije" +msgstr "Dejanja" #. module: base #: selection:res.request,priority:0 @@ -8530,13 +8549,13 @@ msgstr "Vključi/izključi skupno RML glavo" #. module: base #: help:workflow.transition,act_to:0 msgid "The destination activity." -msgstr "" +msgstr "Cilj aktivnosti." #. module: base #: view:base.module.update:0 #: view:base.update.translations:0 msgid "Update" -msgstr "Osveži" +msgstr "Posodobi" #. module: base #: model:ir.actions.report.xml,name:base.ir_module_reference_print @@ -8556,7 +8575,7 @@ msgstr "" #. module: base #: selection:ir.model.fields,select_level:0 msgid "Advanced Search (deprecated)" -msgstr "" +msgstr "Napredno iskanje (opuščeno)" #. module: base #: model:res.country,name:base.cx @@ -8571,7 +8590,7 @@ msgstr "Konfiguracija ostalih akcij" #. module: base #: view:res.config.installer:0 msgid "Install Modules" -msgstr "" +msgstr "Namesti module" #. module: base #: model:ir.actions.act_window,name:base.res_partner_canal-act @@ -8584,13 +8603,13 @@ msgstr "Kanali" #. module: base #: view:ir.ui.view:0 msgid "Extra Info" -msgstr "" +msgstr "Dodatne informacije" #. module: base #: model:ir.actions.act_window,name:base.act_values_form_action #: model:ir.ui.menu,name:base.menu_values_form_action msgid "Client Events" -msgstr "" +msgstr "Dogodki klienta" #. module: base #: view:ir.module.module:0 @@ -8600,18 +8619,18 @@ msgstr "Daj v seznam za namestitev" #. module: base #: model:ir.model,name:base.model_partner_wizard_ean_check msgid "Ean Check" -msgstr "" +msgstr "Preveri EAN" #. module: base #: sql_constraint:res.config.users:0 #: sql_constraint:res.users:0 msgid "You can not have two users with the same login !" -msgstr "" +msgstr "Ne morete imeti dva uporabnika z istim prijavnim imenom!" #. module: base #: model:ir.model,name:base.model_multi_company_default msgid "Default multi company" -msgstr "" +msgstr "Privzeto multi podjetje" #. module: base #: view:res.request:0 @@ -8622,7 +8641,7 @@ msgstr "Pošlji" #: field:res.config.users,menu_tips:0 #: field:res.users,menu_tips:0 msgid "Menu Tips" -msgstr "" +msgstr "Namigi menija" #. module: base #: field:ir.translation,src:0 @@ -8632,7 +8651,7 @@ msgstr "Vir" #. module: base #: help:res.partner.address,partner_id:0 msgid "Keep empty for a private address, not related to partner." -msgstr "" +msgstr "Pustite prazno za zasebni naslov, ki ni povezan s partnerjem." #. module: base #: model:res.country,name:base.vu @@ -8662,14 +8681,14 @@ msgstr "Začni s konfiguracijo" #. module: base #: view:base.language.export:0 msgid "_Export" -msgstr "" +msgstr "_Izvoz" #. module: base #: field:base.language.install,state:0 #: field:base.module.import,state:0 #: field:base.module.update,state:0 msgid "state" -msgstr "" +msgstr "država" #. module: base #: selection:base.language.install,lang:0 @@ -8693,6 +8712,8 @@ msgid "" "Invalid group_by specification: \"%s\".\n" "A group_by specification must be a list of valid fields." msgstr "" +"Neveljavna specifikacija group_by: \"%s\".\n" +"Specifikacija group_by mora biti seznam veljavnih polj." #. module: base #: model:res.country,name:base.sa @@ -8705,22 +8726,24 @@ msgid "" "Check this box if the partner is a supplier. If it's not checked, purchase " "people will not see it when encoding a purchase order." msgstr "" +"Označite to polje, če je partner dobavitelj. Če ni označeno, ljudje, ki " +"skrbijo za nabavo, ne bodo videli dobavitelja ob izdelavi naročila." #. module: base #: field:ir.model.fields,relation_field:0 msgid "Relation Field" -msgstr "" +msgstr "Polje odnosa" #. module: base #: view:res.partner.event:0 msgid "Event Logs" -msgstr "" +msgstr "Dnevniki dogodka" #. module: base #: code:addons/base/module/wizard/base_module_configuration.py:37 #, python-format msgid "System Configuration done" -msgstr "" +msgstr "Konfiguracija sistema je končana" #. module: base #: field:workflow.triggers,instance_id:0 @@ -8731,7 +8754,7 @@ msgstr "Ciljni primerek" #: field:ir.actions.act_window,multi:0 #: field:ir.actions.wizard,multi:0 msgid "Action on Multiple Doc." -msgstr "" +msgstr "Dejanje na večih dokumentih" #. module: base #: view:base.language.export:0 @@ -8746,7 +8769,7 @@ msgstr "XML pot" #. module: base #: selection:ir.actions.todo,restart:0 msgid "On Skip" -msgstr "" +msgstr "Na preskoku" #. module: base #: model:res.country,name:base.gn @@ -8763,19 +8786,20 @@ msgstr "Luksemburg" msgid "" "The kind of action or button in the client side that will trigger the action." msgstr "" +"Vrste dejavnosti ali gumba na strani odjemalca, ki bo sprožila dejanje." #. module: base #: code:addons/base/ir/ir_ui_menu.py:285 #, python-format msgid "Error ! You can not create recursive Menu." -msgstr "" +msgstr "Napaka! Ne morete ustvariti rekurzivnega menija." #. module: base #: model:ir.actions.act_window,name:base.action_publisher_warranty_contract_add_wizard #: model:ir.ui.menu,name:base.menu_publisher_warranty_contract_add #: view:publisher_warranty.contract.wizard:0 msgid "Register a Contract" -msgstr "" +msgstr "Registriraj pogodbo" #. module: base #: view:ir.rule:0 @@ -8783,12 +8807,15 @@ msgid "" "3. If user belongs to several groups, the results from step 2 are combined " "with logical OR operator" msgstr "" +"3. Če uporabnik pripada večim skupinam, je rezultat od koraka 2 v " +"kombinaciji z logičnim OR operatorjem." #. module: base #: code:addons/base/publisher_warranty/publisher_warranty.py:145 #, python-format msgid "Please check your publisher warranty contract name and validity." msgstr "" +"Prosim, preverite ime pogodbe založniške garancije in njeno veljavnost." #. module: base #: model:res.country,name:base.sv @@ -8828,7 +8855,7 @@ msgstr "Tajska" #. module: base #: model:ir.ui.menu,name:base.menu_crm_config_lead msgid "Leads & Opportunities" -msgstr "" +msgstr "Interesi & priložnosti" #. module: base #: selection:base.language.install,lang:0 @@ -8838,7 +8865,7 @@ msgstr "" #. module: base #: view:res.log:0 msgid "System Logs" -msgstr "" +msgstr "Sistemski dnevniki" #. module: base #: selection:workflow.activity,join_mode:0 @@ -8849,7 +8876,7 @@ msgstr "In" #. module: base #: field:ir.model.fields,relation:0 msgid "Object Relation" -msgstr "Relacija objekta" +msgstr "Relacija predmeta" #. module: base #: view:ir.rule:0 @@ -8871,7 +8898,7 @@ msgstr "ir.actions.act_window" #. module: base #: field:ir.rule,perm_create:0 msgid "Apply For Create" -msgstr "" +msgstr "Uporabi za ustvaritev" #. module: base #: model:res.country,name:base.vi @@ -8897,6 +8924,10 @@ msgid "" "be assigned to specific groups in order to make them accessible to some " "users within the system." msgstr "" +"Upravljajte in prilagodite predmete, ki so na voljo in se prikažejo v meniju " +"sistem OpenERP. Lahko izbrišete element s klikom na polje na začetku vsake " +"vrstice in nato izbrišete s pomočjo gumba, ki se prikaže. Predmeti se lahko " +"dodelijo posebnim skupinam, da so na voljo nekateri uporabnikom v sistemu." #. module: base #: field:ir.ui.view,field_parent:0 @@ -8911,7 +8942,7 @@ msgstr "Podrejeno polje" #: field:ir.actions.server,usage:0 #: field:ir.actions.wizard,usage:0 msgid "Action Usage" -msgstr "Raba akcije" +msgstr "Rava dejanja" #. module: base #: model:ir.model,name:base.model_workflow_workitem @@ -8931,25 +8962,25 @@ msgstr "Pogled:" #. module: base #: field:ir.model.fields,view_load:0 msgid "View Auto-Load" -msgstr "" +msgstr "Pogled samodejnega nalaganja" #. module: base #: code:addons/base/ir/ir_model.py:232 #, python-format msgid "You cannot remove the field '%s' !" -msgstr "" +msgstr "Ne morete odstraniti polja: '%s'!" #. module: base #: field:ir.exports,resource:0 #: view:ir.property:0 #: field:ir.property,res_id:0 msgid "Resource" -msgstr "Resurs" +msgstr "Vir" #. module: base #: field:ir.ui.menu,web_icon:0 msgid "Web Icon File" -msgstr "" +msgstr "Datoteka spletne ikone" #. module: base #: selection:base.language.install,lang:0 @@ -8959,13 +8990,13 @@ msgstr "" #. module: base #: view:ir.actions.act_window:0 msgid "View Ordering" -msgstr "" +msgstr "Pogled naročanja" #. module: base #: code:addons/base/module/wizard/base_module_upgrade.py:95 #, python-format msgid "Unmet dependency !" -msgstr "" +msgstr "Neizpolnjene odvisnosti!" #. module: base #: view:base.language.import:0 @@ -8973,6 +9004,8 @@ msgid "" "Supported file formats: *.csv (Comma-separated values) or *.po (GetText " "Portable Objects)" msgstr "" +"Podprti fotmati datoteke: *.csv (Comma-separated values) ali *.po (GetText " +"Portable Objects)" #. module: base #: code:addons/base/ir/ir_model.py:487 @@ -8981,11 +9014,13 @@ msgid "" "You can not delete this document (%s) ! Be sure your user belongs to one of " "these groups: %s." msgstr "" +"Ne morete izbrisati tega dokumenta (%s)! Bodite prepričani, da vaš uporabnik " +"pripada eni od teh skupin: %s." #. module: base #: model:ir.model,name:base.model_base_module_configuration msgid "base.module.configuration" -msgstr "" +msgstr "base.module.configuration" #. module: base #: field:base.language.export,name:0 @@ -9007,7 +9042,7 @@ msgstr "Slovaška republika" #. module: base #: model:ir.ui.menu,name:base.publisher_warranty msgid "Publisher Warranty" -msgstr "" +msgstr "Založniška garancija" #. module: base #: model:res.country,name:base.aw @@ -9049,17 +9084,17 @@ msgstr "Razčlenjenost" #: view:res.users:0 #: field:res.users,company_id:0 msgid "Company" -msgstr "Družba" +msgstr "Podjetje" #. module: base #: view:res.users:0 msgid "Email & Signature" -msgstr "" +msgstr "E-pošta & podpis" #. module: base #: view:publisher_warranty.contract:0 msgid "Publisher Warranty Contract" -msgstr "" +msgstr "Pogodba založniške garancije" #. module: base #: selection:base.language.install,lang:0 @@ -9069,12 +9104,12 @@ msgstr "" #. module: base #: model:ir.ui.menu,name:base.menu_aftersale msgid "After-Sale Services" -msgstr "" +msgstr "Storitve po prodaji" #. module: base #: view:ir.actions.todo:0 msgid "Launch" -msgstr "" +msgstr "Zaženi" #. module: base #: field:ir.actions.act_window,limit:0 @@ -9084,7 +9119,7 @@ msgstr "Meja" #. module: base #: help:ir.actions.server,wkf_model_id:0 msgid "Workflow to be executed on this model." -msgstr "" +msgstr "Delovni proces, ki naj bo izvršen na tem modelu." #. module: base #: model:res.country,name:base.jm @@ -9099,6 +9134,10 @@ msgid "" "categories have a hierarchy structure: a partner belonging to a category " "also belong to his parent category." msgstr "" +"Upravljanje kategorij partnerjev, da bi jih bolje razvrstili za namen " +"sledenja in analiz. Partner lahko pripada večim kategorijam in kategorijam, " +"ki imajo hierarhično strukturo: partner, ki spada v kategorijo, spada tudi " +"v njegovo matično kategorijo." #. module: base #: model:res.country,name:base.az @@ -9125,7 +9164,7 @@ msgstr "Deviški otoki (britanski)" #: view:ir.property:0 #: model:ir.ui.menu,name:base.next_id_15 msgid "Parameters" -msgstr "" +msgstr "Parametri" #. module: base #: selection:base.language.install,lang:0 @@ -9145,6 +9184,10 @@ msgid "" "uncheck the 'Suppliers' filter button in order to search in all your " "partners, including customers and prospects." msgstr "" +"Lahko dostopate do vseh informacij o vaši dobaviteljih iz obrazca " +"dobavitelji: računovodski podatki, zgodovina e-pošte, sestankov, nakupi, itd " +"Lahko odznačite gumb filter \"Dobavitelji\" za iskanje vseh vaših " +"partnerjev, vključno s kupci in obeti .." #. module: base #: model:res.country,name:base.rw @@ -9154,7 +9197,7 @@ msgstr "Ruanda" #. module: base #: view:ir.sequence:0 msgid "Day of the week (0:Monday): %(weekday)s" -msgstr "" +msgstr "Dan v tednu (0:Ponedeljek): %(weekday)s" #. module: base #: model:res.country,name:base.ck @@ -9164,7 +9207,7 @@ msgstr "Cookovi otoki" #. module: base #: field:ir.model.data,noupdate:0 msgid "Non Updatable" -msgstr "Neposodobljivo" +msgstr "Ni mogoče posodobiti" #. module: base #: selection:base.language.install,lang:0 @@ -9184,7 +9227,7 @@ msgstr "Trenutno okno" #. module: base #: view:ir.values:0 msgid "Action Source" -msgstr "" +msgstr "Vir dejanja" #. module: base #: view:res.config.view:0 @@ -9193,6 +9236,9 @@ msgid "" "simplified interface, which has less features but is easier. You can always " "switch later from the user preferences." msgstr "" +"Če uporabljate OpenERP prvičkrat, potem vam močno priporočamo, da izberete " +"poenostavljen vmesnik, ki ima manj možnosti, ampak je lažji za uporabo. " +"Vedno lahko prestavite način prikaza vmesnika v nastavitvah uporabnika." #. module: base #: model:ir.model,name:base.model_res_country @@ -9210,12 +9256,12 @@ msgstr "Država" #: field:ir.model.fields,complete_name:0 #: field:ir.ui.menu,complete_name:0 msgid "Complete Name" -msgstr "Celotni naziv" +msgstr "Celotno ime" #. module: base #: field:ir.values,object:0 msgid "Is Object" -msgstr "Je objekt" +msgstr "Je predmet" #. module: base #: view:ir.rule:0 @@ -9223,6 +9269,8 @@ msgid "" "1. Global rules are combined together with a logical AND operator, and with " "the result of the following steps" msgstr "" +"1. Globalna pravila so združena skupaj z logičnim AND operatorjem in z " +"rezultatom naslednjih korakov" #. module: base #: field:res.partner.category,name:0 @@ -9232,17 +9280,17 @@ msgstr "Naziv kategorije" #. module: base #: model:res.partner.category,name:base.res_partner_category_15 msgid "IT sector" -msgstr "" +msgstr "IT sektor" #. module: base #: view:ir.actions.act_window:0 msgid "Select Groups" -msgstr "" +msgstr "Izberite skupine" #. module: base #: view:res.lang:0 msgid "%X - Appropriate time representation." -msgstr "" +msgstr "%X - primeren čas zastopnja" #. module: base #: selection:base.language.install,lang:0 @@ -9257,6 +9305,10 @@ msgid "" "1,06,500;[1,2,-1] will represent it to be 106,50,0;[3] will represent it as " "106,500. Provided ',' as the thousand separator in each case." msgstr "" +"Oblika ločila mora biti kot [,n] kjer je 0 < n : začetko od enote števila. - " +"1 bo končalo ločevanje, npr [3,2,-1] bo predstavljalo 106500 kot 1,06,500; " +"[1,2,-1] bo predstavljajo kot 106,50,0; [3] bo predstavljalo kot 1060,500. " +"Določena ',' kot ločilo tisoč v vsakem primeru." #. module: base #: view:res.company:0 @@ -9267,7 +9319,7 @@ msgstr "Pokočno" #: code:addons/base/ir/ir_model.py:317 #, python-format msgid "Can only rename one column at a time!" -msgstr "" +msgstr "Preimenujete lahko samo en stolpec naenkrat!" #. module: base #: selection:ir.translation,type:0 @@ -9277,7 +9329,7 @@ msgstr "Gumb čarovnika" #. module: base #: selection:ir.translation,type:0 msgid "Report/Template" -msgstr "" +msgstr "Poročilo/predloga" #. module: base #: selection:ir.actions.act_window.view,view_mode:0 @@ -9311,7 +9363,7 @@ msgstr "Čarovniki za konfiguracijo" #. module: base #: field:res.lang,code:0 msgid "Locale Code" -msgstr "" +msgstr "Lokalna koda" #. module: base #: field:workflow.activity,split_mode:0 @@ -9321,7 +9373,7 @@ msgstr "Način ločevanja" #. module: base #: view:base.module.upgrade:0 msgid "Note that this operation might take a few minutes." -msgstr "" +msgstr "Upoštevajte, da lahko ta postopek traja nekaj minut." #. module: base #: model:ir.ui.menu,name:base.menu_localisation @@ -9331,12 +9383,12 @@ msgstr "Lokalizacija" #. module: base #: view:ir.actions.server:0 msgid "Action to Launch" -msgstr "" +msgstr "Dejanje za sprožitev" #. module: base #: view:ir.cron:0 msgid "Execution" -msgstr "" +msgstr "Izvršitev" #. module: base #: field:ir.actions.server,condition:0 @@ -9347,7 +9399,7 @@ msgstr "Pogoj" #. module: base #: help:ir.values,model_id:0 msgid "This field is not used, it only helps you to select a good model." -msgstr "" +msgstr "Polje ni uporabljeno, pomaga vam samo izbrati dober model." #. module: base #: field:ir.ui.view,name:0 @@ -9362,7 +9414,7 @@ msgstr "Italijansko" #. module: base #: field:ir.actions.report.xml,attachment:0 msgid "Save As Attachment Prefix" -msgstr "" +msgstr "Shrani kot pripono priloge" #. module: base #: view:ir.actions.server:0 @@ -9370,16 +9422,18 @@ msgid "" "Only one client action will be executed, last client action will be " "considered in case of multiple client actions." msgstr "" +"Samo eno dejanje klienta bo izvršeno, zadnje dejanje klienta se bo " +"obravnavalo v primeru večkratnih dejanj klienta." #. module: base #: view:res.lang:0 msgid "%j - Day of the year [001,366]." -msgstr "" +msgstr "%j - dan v letu[001,366]" #. module: base #: field:ir.actions.server,mobile:0 msgid "Mobile No" -msgstr "" +msgstr "Številka mobilnega telefona" #. module: base #: model:ir.actions.act_window,name:base.action_partner_by_category @@ -9393,7 +9447,7 @@ msgstr "Partnerjeve kategorije" #. module: base #: view:base.module.upgrade:0 msgid "System Update" -msgstr "" +msgstr "Posodobitev sistema" #. module: base #: selection:ir.translation,type:0 @@ -9403,7 +9457,7 @@ msgstr "Polje čarovnika" #. module: base #: help:ir.sequence,prefix:0 msgid "Prefix value of the record for the sequence" -msgstr "" +msgstr "Predpona vrednosti zapisa za zaporedje" #. module: base #: model:res.country,name:base.sc @@ -9441,23 +9495,23 @@ msgstr "Lastnik konta" #: code:addons/base/res/res_user.py:256 #, python-format msgid "Company Switch Warning" -msgstr "" +msgstr "Opozorilo preklopa podjetja" #. module: base #: model:ir.actions.act_window,name:base.action_res_widget_wizard msgid "Homepage Widgets Management" -msgstr "" +msgstr "Upravljanje domače strani gradnikov" #. module: base #: field:workflow,osv:0 #: field:workflow.instance,res_type:0 msgid "Resource Object" -msgstr "Objekt resursa" +msgstr "Predmet vira" #. module: base #: help:ir.sequence,number_increment:0 msgid "The next number of the sequence will be incremented by this number" -msgstr "" +msgstr "Naslednje število zaporedja bo to število povečalo za to številko" #. module: base #: field:ir.cron,function:0 @@ -9469,12 +9523,12 @@ msgstr "Funkcija" #. module: base #: view:res.widget:0 msgid "Search Widget" -msgstr "" +msgstr "Iskanje gradnika" #. module: base #: selection:ir.actions.todo,restart:0 msgid "Never" -msgstr "" +msgstr "Nikoli" #. module: base #: selection:res.partner.address,type:0 @@ -9495,7 +9549,7 @@ msgstr "Gvineja Bissau" #. module: base #: view:workflow.instance:0 msgid "Workflow Instances" -msgstr "Primerki poteka dela" +msgstr "Primeri delovnega procesa" #. module: base #: code:addons/base/res/partner/partner.py:261 @@ -9511,13 +9565,13 @@ msgstr "Severna Koreja" #. module: base #: selection:ir.actions.server,state:0 msgid "Create Object" -msgstr "Ustvari objekt" +msgstr "Ustvari predmet" #. module: base #: view:ir.filters:0 #: field:res.log,context:0 msgid "Context" -msgstr "" +msgstr "Zveza" #. module: base #: field:res.bank,bic:0 @@ -9545,8 +9599,8 @@ msgid "" "Used to select automatically the right address according to the context in " "sales and purchases documents." msgstr "" -"Običajno se samodejno izbere pravi naslov glede na kontest prodajnih in " -"nabavnih dokumentov." +"Uporabljeno za avtomatično izbiro pravega naslova, ki se nanaša na zvezo " +"dokumentov prodaje in nabave." #. module: base #: model:res.country,name:base.lk diff --git a/openerp/addons/base/ir/ir.xml b/openerp/addons/base/ir/ir.xml index 4766134a70c..7989bf6e180 100644 --- a/openerp/addons/base/ir/ir.xml +++ b/openerp/addons/base/ir/ir.xml @@ -7,44 +7,50 @@ ir.values.form.action ir.values form - 20 -
- + + - + + - + - + + + + + +
- - + + ir.values.form.defaults + ir.values + form + +
+ + + + + + + - - - - + + + - - - - - - - - - - - +
@@ -53,10 +59,9 @@ ir.values tree - + - @@ -73,7 +78,7 @@ - + @@ -81,23 +86,21 @@ - Client Events + Action Bindings ir.actions.act_window ir.values form tree,form [('key','=','action')] - {'read':'default','default_object':1} + {'default_key':'action'} - tree - form @@ -105,54 +108,27 @@ - - - - - - - ir.values.form - ir.values - form - -
- - - - - - - - - - - - -
- - - ir.values.tree - ir.values - tree - - - - - - - - - - - - - - Client Actions Connections + + User-defined Defaults ir.actions.act_window ir.values form - - {'read':'default'} + tree,form + + [('key','=','default')] + {'default_key':'default','default_key2':''} + + + + tree + + + + + + form + + @@ -344,6 +320,7 @@ + diff --git a/openerp/addons/base/ir/ir_mail_server.py b/openerp/addons/base/ir/ir_mail_server.py index e8d97160068..f175a27077f 100644 --- a/openerp/addons/base/ir/ir_mail_server.py +++ b/openerp/addons/base/ir/ir_mail_server.py @@ -236,15 +236,18 @@ class ir_mail_server(osv.osv): return connection def build_email(self, email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False, - attachments=None, message_id=None, references=None, object_id=False, subtype='plain', headers=None): + attachments=None, message_id=None, references=None, object_id=False, subtype='plain', headers=None, + body_alternative=None, subtype_alternative='plain'): """Constructs an RFC2822 email.message.Message object based on the keyword arguments passed, and returns it. :param string email_from: sender email address :param list email_to: list of recipient addresses (to be joined with commas) :param string subject: email subject (no pre-encoding/quoting necessary) - :param string body: email body, according to the ``subtype`` (by default, plaintext). + :param string body: email body, of the type ``subtype`` (by default, plaintext). If html subtype is used, the message will be automatically converted - to plaintext and wrapped in multipart/alternative. + to plaintext and wrapped in multipart/alternative, unless an explicit + ``body_alternative`` version is passed. + :param string body_alternative: optional alternative body, of the type specified in ``subtype_alternative`` :param string reply_to: optional value of Reply-To header :param string object_id: optional tracking identifier, to be included in the message-id for recognizing replies. Suggested format for object-id is "res_id-model", @@ -252,6 +255,8 @@ class ir_mail_server(osv.osv): :param string subtype: optional mime subtype for the text body (usually 'plain' or 'html'), must match the format of the ``body`` parameter. Default is 'plain', making the content part of the mail "text/plain". + :param string subtype_alternative: optional mime subtype of ``body_alternative`` (usually 'plain' + or 'html'). Default is 'plain'. :param list attachments: list of (filename, filecontents) pairs, where filecontents is a string containing the bytes of the attachment :param list email_cc: optional list of string values for CC header (to be joined with commas) @@ -276,7 +281,7 @@ class ir_mail_server(osv.osv): if not body: body = u'' email_body_utf8 = ustr(body).encode('utf-8') - email_text_part = MIMEText(email_body_utf8 or '', _subtype=subtype, _charset='utf-8') + email_text_part = MIMEText(email_body_utf8, _subtype=subtype, _charset='utf-8') msg = MIMEMultipart() if not message_id: @@ -304,13 +309,21 @@ class ir_mail_server(osv.osv): for key, value in headers.iteritems(): msg[ustr(key).encode('utf-8')] = encode_header(value) - if html2text and subtype == 'html': - # Always provide alternative text body if possible. + if subtype == 'html' and not body_alternative and html2text: + # Always provide alternative text body ourselves if possible. text_utf8 = tools.html2text(email_body_utf8.decode('utf-8')).encode('utf-8') alternative_part = MIMEMultipart(_subtype="alternative") alternative_part.attach(MIMEText(text_utf8, _charset='utf-8', _subtype='plain')) alternative_part.attach(email_text_part) msg.attach(alternative_part) + elif body_alternative: + # Include both alternatives, as specified, within a multipart/alternative part + alternative_part = MIMEMultipart(_subtype="alternative") + body_alternative_utf8 = ustr(body_alternative).encode('utf-8') + alternative_body_part = MIMEText(body_alternative_utf8, _subtype=subtype_alternative, _charset='utf-8') + alternative_part.attach(alternative_body_part) + alternative_part.attach(email_text_part) + msg.attach(alternative_part) else: msg.attach(email_text_part) diff --git a/openerp/addons/base/ir/ir_values.py b/openerp/addons/base/ir/ir_values.py index f079e5764c9..15e8f3bf3ce 100644 --- a/openerp/addons/base/ir/ir_values.py +++ b/openerp/addons/base/ir/ir_values.py @@ -28,19 +28,89 @@ EXCLUDED_FIELDS = set(( 'report_sxw_content', 'report_rml_content', 'report_sxw', 'report_rml', 'report_sxw_content_data', 'report_rml_content_data', 'search_view', )) +#: Possible slots to bind an action to with :meth:`~.set_action` +ACTION_SLOTS = [ + "client_action_multi", # sidebar wizard action + "client_print_multi", # sidebar report printing button + "client_action_relate", # sidebar related link + "tree_but_open", # double-click on item in tree view + "tree_but_action", # deprecated: same as tree_but_open + ] + + class ir_values(osv.osv): + """Holds internal model-specific action bindings and user-defined default + field values. definitions. This is a legacy internal model, mixing + two different concepts, and will likely be updated or replaced in a + future version by cleaner, separate models. You should not depend + explicitly on it. + + The purpose of each ``ir.values`` entry depends on its type, defined + by the ``key`` column: + + * 'default': user-defined default values, used when creating new + records of this model: + * 'action': binding of an action to a particular *action slot* of + this model, making the action easily available in the user + interface for this model. + + The ``key2`` column acts as a qualifier, further refining the type + of the entry. The possible values are: + + * for 'default' entries: an optional condition restricting the + cases where this particular default value will be applicable, + or ``False`` for no condition + * for 'action' entries: the ``key2`` qualifier is one of the available + action slots, defining how this action can be invoked: + + * ``'client_print_multi'`` for report printing actions that will + be available on views displaying items from this model + * ``'client_action_multi'`` for assistants (wizards) actions + that will be available in views displaying objects of this model + * ``'client_action_relate'`` for links towards related documents + that should be available in views displaying objects of this model + * ``'tree_but_open'`` for actions that will be triggered when + double-clicking an item from this model in a hierarchical tree view + + Each entry is specific to a model (``model`` column), and for ``'actions'`` + type, may even be made specific to a given record of that model when the + ``res_id`` column contains a record ID (``False`` means it's global for + all records). + + The content of the entry is defined by the ``value`` column, which may either + contain an arbitrary value, or a reference string defining the action that + should be executed. + + .. rubric:: Usage: default values + + The ``'default'`` entries are usually defined manually by the + users, and set by their UI clients calling :meth:`~.set_default`. + These default values are then automatically used by the + ORM every time a new record is about to be created, i.e. when + :meth:`~openerp.osv.osv.osv.default_get` + or :meth:`~openerp.osv.osv.osv.create` are called. + + .. rubric:: Usage: action bindings + + Business applications will usually bind their actions during + installation, and OpenERP UI clients will apply them as defined, + based on the list of actions included in the result of + :meth:`~openerp.osv.osv.osv.fields_view_get`, + or directly returned by explicit calls to :meth:`~.get_actions`. + """ _name = 'ir.values' def _value_unpickle(self, cursor, user, ids, name, arg, context=None): res = {} - for report in self.browse(cursor, user, ids, context=context): - value = report[name[:-9]] - if not report.object and value: + for record in self.browse(cursor, user, ids, context=context): + value = record[name[:-9]] + if record.key == 'default' and value: + # default values are pickled on the fly try: value = str(pickle.loads(value)) - except: + except Exception: pass - res[report.id] = value + res[record.id] = value return res def _value_pickle(self, cursor, user, id, name, value, arg, context=None): @@ -49,18 +119,20 @@ class ir_values(osv.osv): ctx = context.copy() if self.CONCURRENCY_CHECK_FIELD in ctx: del ctx[self.CONCURRENCY_CHECK_FIELD] - if not self.browse(cursor, user, id, context=context).object: + record = self.browse(cursor, user, id, context=context) + if record.key == 'default': + # default values are pickled on the fly value = pickle.dumps(value) self.write(cursor, user, id, {name[:-9]: value}, context=ctx) - def onchange_object_id(self, cr, uid, ids, object_id, context={}): + def onchange_object_id(self, cr, uid, ids, object_id, context=None): if not object_id: return {} act = self.pool.get('ir.model').browse(cr, uid, object_id, context=context) return { 'value': {'model': act.model} } - def onchange_action_id(self, cr, uid, ids, action_id, context={}): + def onchange_action_id(self, cr, uid, ids, action_id, context=None): if not action_id: return {} act = self.pool.get('ir.actions.actions').browse(cr, uid, action_id, context=context) return { @@ -68,32 +140,47 @@ class ir_values(osv.osv): } _columns = { - 'name': fields.char('Name', size=128), - 'model_id': fields.many2one('ir.model', 'Object', size=128, - help="This field is not used, it only helps you to select a good model."), - 'model': fields.char('Object Name', size=128, select=True), - 'action_id': fields.many2one('ir.actions.actions', 'Action', - help="This field is not used, it only helps you to select the right action."), - 'value': fields.text('Value'), + 'name': fields.char('Name', size=128, required=True), + 'model': fields.char('Model Name', size=128, select=True, required=True, + help="Model to which this entry applies"), + + # TODO: model_id and action_id should be read-write function fields + 'model_id': fields.many2one('ir.model', 'Model (change only)', size=128, + help="Model to which this entry applies - " + "helper field for setting a model, will " + "automatically set the correct model name"), + 'action_id': fields.many2one('ir.actions.actions', 'Action (change only)', + help="Action bound to this entry - " + "helper field for binding an action, will " + "automatically set the correct reference"), + + 'value': fields.text('Value', help="Default value (pickled) or reference to an action"), 'value_unpickle': fields.function(_value_unpickle, fnct_inv=_value_pickle, - method=True, type='text', string='Value'), - 'object': fields.boolean('Is Object'), - 'key': fields.selection([('action','Action'),('default','Default')], 'Type', size=128, select=True), - 'key2' : fields.char('Event Type', size=128, select=True, help="The kind of action or button on the client side " - "that will trigger the action. One of: " - "client_action_multi, client_action_relate, tree_but_open, " - "client_print_multi"), - 'meta': fields.text('Meta Datas'), - 'meta_unpickle': fields.function(_value_unpickle, fnct_inv=_value_pickle, - method=True, type='text', string='Metadata'), - 'res_id': fields.integer('Object ID', help="Keep 0 if the action must appear on all resources.", select=True), - 'user_id': fields.many2one('res.users', 'User', ondelete='cascade', select=True), - 'company_id': fields.many2one('res.company', 'Company', select=True) + type='text', + string='Default value or action reference'), + 'key': fields.selection([('action','Action'),('default','Default')], + 'Type', size=128, select=True, required=True, + help="- Action: an action attached to one slot of the given model\n" + "- Default: a default value for a model field"), + 'key2' : fields.char('Qualifier', size=128, select=True, + help="For actions, one of the possible action slots: \n" + " - client_action_multi\n" + " - client_print_multi\n" + " - client_action_relate\n" + " - tree_but_open\n" + "For defaults, an optional condition" + ,), + 'res_id': fields.integer('Record ID', select=True, + help="Database identifier of the record to which this applies. " + "0 = for all records"), + 'user_id': fields.many2one('res.users', 'User', ondelete='cascade', select=True, + help="If set, action binding only applies for this user."), + 'company_id': fields.many2one('res.company', 'Company', ondelete='cascade', select=True, + help="If set, action binding only applies for this company") } _defaults = { - 'key': lambda *a: 'action', - 'key2': lambda *a: 'tree_but_open', - 'company_id': lambda *a: False + 'key': 'action', + 'key2': 'tree_but_open', } def _auto_init(self, cr, context=None): @@ -102,140 +189,271 @@ class ir_values(osv.osv): if not cr.fetchone(): cr.execute('CREATE INDEX ir_values_key_model_key2_res_id_user_id_idx ON ir_values (key, model, key2, res_id, user_id)') - def set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=False, preserve_user=False, company=False): + def set_default(self, cr, uid, model, field_name, value, for_all_users=True, company_id=False, condition=False): + """Defines a default value for the given model and field_name. Any previous + default for the same scope (model, field_name, value, for_all_users, company_id, condition) + will be replaced and lost in the process. + + Defaults can be later retrieved via :meth:`~.get_defaults`, which will return + the highest priority default for any given field. Defaults that are more specific + have a higher priority, in the following order (highest to lowest): + + * specific to user and company + * specific to user only + * specific to company only + * global to everyone + + :param string model: model name + :param string field_name: field name to which the default applies + :param value: the default field value to set + :type value: any serializable Python value + :param bool for_all_users: whether the default should apply to everybody or only + the user calling the method + :param int company_id: optional ID of the company to which the default should + apply. If omitted, the default will be global. If True + is passed, the current user's company will be used. + :param string condition: optional condition specification that can be used to + restrict the applicability of the default values + (e.g. based on another field's value). This is an + opaque string as far as the API is concerned, but client + stacks typically use single-field conditions in the + form ``'key=stringified_value'``. + (Currently, the condition is trimmed to 200 characters, + so values that share the same first 200 characters always + match) + :return: id of the newly created ir.values entry + """ if isinstance(value, unicode): value = value.encode('utf8') - if not isobject: - value = pickle.dumps(value) - if meta: - meta = pickle.dumps(meta) - assert isinstance(models, (list, tuple)), models - ids_res = [] - for model in models: - if isinstance(model, (list, tuple)): - model,res_id = model - else: - res_id = False - if replace: - search_criteria = [ - ('key', '=', key), - ('key2', '=', key2), - ('model', '=', model), - ('res_id', '=', res_id), - ('user_id', '=', preserve_user and uid) - ] - if key in ('meta', 'default'): - search_criteria.append(('name', '=', name)) - else: - search_criteria.append(('value', '=', value)) + if company_id is True: + # should be company-specific, need to get company id + user = self.pool.get('res.users').browse(cr, uid, uid) + company_id = user.company_id.id - self.unlink(cr, uid, self.search(cr, uid, search_criteria)) - vals = { - 'name': name, - 'value': value, - 'model': model, - 'object': isobject, - 'key': key, - 'key2': key2 and key2[:200], - 'meta': meta, - 'user_id': preserve_user and uid, - } - if company: - cid = self.pool.get('res.users').browse(cr, uid, uid, context={}).company_id.id - vals['company_id']=cid - if res_id: - vals['res_id']= res_id - ids_res.append(self.create(cr, uid, vals)) - return ids_res + # remove existing defaults for the same scope + search_criteria = [ + ('key', '=', 'default'), + ('key2', '=', condition and condition[:200]), + ('model', '=', model), + ('name', '=', field_name), + ('user_id', '=', False if for_all_users else uid), + ('company_id','=', company_id) + ] + self.unlink(cr, uid, self.search(cr, uid, search_criteria)) + + return self.create(cr, uid, { + 'name': field_name, + 'value': pickle.dumps(value), + 'model': model, + 'key': 'default', + 'key2': condition and condition[:200], + 'user_id': False if for_all_users else uid, + 'company_id': company_id, + }) + + def get_defaults(self, cr, uid, model, condition=False): + """Returns any default values that are defined for the current model and user, + (and match ``condition``, if specified), previously registered via + :meth:`~.set_default`. + + Defaults are global to a model, not field-specific, but an optional + ``condition`` can be provided to restrict matching default values + to those that were defined for the same condition (usually based + on another field's value). + + Default values also have priorities depending on whom they apply + to: only the highest priority value will be returned for any + field. See :meth:`~.set_default` for more details. + + :param string model: model name + :param string condition: optional condition specification that can be used to + restrict the applicability of the default values + (e.g. based on another field's value). This is an + opaque string as far as the API is concerned, but client + stacks typically use single-field conditions in the + form ``'key=stringified_value'``. + (Currently, the condition is trimmed to 200 characters, + so values that share the same first 200 characters always + match) + :return: list of default values tuples of the form ``(id, field_name, value)`` + (``id`` is the ID of the default entry, usually irrelevant) + """ + # use a direct SQL query for performance reasons, + # this is called very often + query = """SELECT v.id, v.name, v.value FROM ir_values v + LEFT JOIN res_users u ON (v.user_id = u.id) + WHERE v.key = %%s AND v.model = %%s + AND (v.user_id = %%s OR v.user_id IS NULL) + AND (v.company_id IS NULL OR + v.company_id = + (SELECT company_id from res_users where id = %%s) + ) + %s + ORDER BY v.user_id, u.company_id""" + query = query % ('AND v.key2 = %s' if condition else '') + params = ('default', model, uid, uid) + if condition: + params += (condition[:200],) + cr.execute(query, params) + + # keep only the highest priority default for each field + defaults = {} + for row in cr.dictfetchall(): + defaults.setdefault(row['name'], + (row['id'], row['name'], pickle.loads(row['value'].encode('utf-8')))) + return defaults.values() + + def set_action(self, cr, uid, name, action_slot, model, action, res_id=False): + """Binds an the given action to the given model's action slot - for later + retrieval via :meth:`~.get_actions`. Any existing binding of the same action + to the same slot is first removed, allowing an update of the action's name. + See the class description for more details about the various action + slots: :class:`~ir_values`. + + :param string name: action label, usually displayed by UI client + :param string action_slot: the action slot to which the action should be + bound to - one of ``client_action_multi``, + ``client_print_multi``, ``client_action_relate``, + ``tree_but_open``. + :param string model: model name + :param string action: action reference, in the form ``'model,id'`` + :param int res_id: optional record id - will bind the action only to a + specific record of the model, not all records. + :return: id of the newly created ir.values entry + """ + assert isinstance(action, basestring) and ',' in action, \ + 'Action definition must be an action reference, e.g. "ir.actions.act_window,42"' + assert action_slot in ACTION_SLOTS, \ + 'Action slot (%s) must be one of: %r' % (action_slot, ACTION_SLOTS) + + # remove existing action definition of same slot and value + search_criteria = [ + ('key', '=', 'action'), + ('key2', '=', action_slot), + ('model', '=', model), + ('res_id', '=', res_id or 0), # int field -> NULL == 0 + ('value', '=', action), + ] + self.unlink(cr, uid, self.search(cr, uid, search_criteria)) + + return self.create(cr, uid, { + 'key': 'action', + 'key2': action_slot, + 'model': model, + 'res_id': res_id, + 'name': name, + 'value': action, + }) + + def get_actions(self, cr, uid, action_slot, model, res_id=False, context=None): + """Retrieves the list of actions bound to the given model's action slot. + See the class description for more details about the various action + slots: :class:`~.ir_values`. + + :param string action_slot: the action slot to which the actions should be + bound to - one of ``client_action_multi``, + ``client_print_multi``, ``client_action_relate``, + ``tree_but_open``. + :param string model: model name + :param int res_id: optional record id - will bind the action only to a + specific record of the model, not all records. + :return: list of action tuples of the form ``(id, name, action_def)``, + where ``id`` is the ID of the default entry, ``name`` is the + action label, and ``action_def`` is a dict containing the + action definition as obtained by calling + :meth:`~openerp.osv.osv.osv.read` on the action record. + """ + assert action_slot in ACTION_SLOTS, 'Illegal action slot value: %s' % action_slot + # use a direct SQL query for performance reasons, + # this is called very often + query = """SELECT v.id, v.name, v.value FROM ir_values v + WHERE v.key = %s AND v.key2 = %s + AND v.model = %s + AND (v.res_id = %s + OR v.res_id IS NULL + OR v.res_id = 0) + ORDER BY v.id""" + cr.execute(query, ('action', action_slot, model, res_id or None)) + results = {} + for action in cr.dictfetchall(): + action_model,id = action['value'].split(',') + fields = [ + field + for field in self.pool.get(action_model)._all_columns + if field not in EXCLUDED_FIELDS] + # FIXME: needs cleanup + try: + action_def = self.pool.get(action_model).read(cr, uid, int(id), fields, context) + if action_def: + if action_model in ('ir.actions.report.xml','ir.actions.act_window', + 'ir.actions.wizard'): + groups = action_def.get('groups_id') + if groups: + cr.execute('SELECT 1 FROM res_groups_users_rel WHERE gid IN %s AND uid=%s', + (tuple(groups), uid)) + if not cr.fetchone(): + if action['name'] == 'Menuitem': + raise osv.except_osv('Error !', + 'You do not have the permission to perform this operation !!!') + continue + # keep only the first action registered for each action name + results[action['name']] = (action['id'], action['name'], action_def) + except except_orm, e: + continue + return results.values() + + def _map_legacy_model_list(self, model_list, map_fn, merge_results=False): + """Apply map_fn to the various models passed, according to + legacy way to specify models/records. + """ + assert isinstance(model_list, (list, tuple)), \ + "model_list should be in the form [model,..] or [(model,res_id), ..]" + results = [] + for model in model_list: + res_id = False + if isinstance(model, (list, tuple)): + model, res_id = model + result = map_fn(model, res_id) + # some of the functions return one result at a time (tuple or id) + # and some return a list of many of them - care for both + if merge_results: + results.extend(result) + else: + results.append(result) + return results + + # Backards-compatibility adapter layer to retrofit into split API + def set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=False, preserve_user=False, company=False): + """Deprecated legacy method to set default values and bind actions to models' action slots. + Now dispatches to the newer API methods according to the value of ``key``: :meth:`~.set_default` + (``key=='default'``) or :meth:`~.set_action` (``key == 'action'``). + + :deprecated: As of v6.1, ``set_default()`` or ``set_action()`` should be used directly. + """ + assert key in ['default', 'action'], "ir.values entry keys must be in ['default','action']" + if key == 'default': + def do_set(model,res_id): + return self.set_default(cr, uid, model, field_name=name, value=value, + for_all_users=(not preserve_user), company_id=company, + condition=key2) + elif key == 'action': + def do_set(model,res_id): + return self.set_action(cr, uid, name, action_slot=key2, model=model, action=value, res_id=res_id) + return self._map_legacy_model_list(models, do_set) def get(self, cr, uid, key, key2, models, meta=False, context=None, res_id_req=False, without_user=True, key2_req=True): - if context is None: - context = {} - result = [] - assert isinstance(models, (list, tuple)), models + """Deprecated legacy method to get the list of default values or actions bound to models' action slots. + Now dispatches to the newer API methods according to the value of ``key``: :meth:`~.get_defaults` + (``key=='default'``) or :meth:`~.get_actions` (``key == 'action'``) - for m in models: - if isinstance(m, (list, tuple)): - m, res_id = m - else: - res_id = False + :deprecated: As of v6.1, ``get_defaults()`` or ``get_actions()`` should be used directly. - where = ['key=%s','model=%s'] - params = [key, str(m)] - if key2: - where.append('key2=%s') - params.append(key2[:200]) - elif key2_req and not meta: - where.append('key2 is null') - if res_id_req and (models[-1][0] == m): - if res_id: - where.append('res_id=%s') - params.append(res_id) - else: - where.append('(res_id is NULL)') - elif res_id: - if (models[-1][0]==m): - where.append('(res_id=%s or (res_id is null))') - params.append(res_id) - else: - where.append('res_id=%s') - params.append(res_id) - order = 'id' - if key == 'default': - # Make sure we get first the values for specific users, then - # the global values. The map/filter below will retain the first - # value for any given name. The 'order by' will put the null - # values last; this may be postgres specific (it is the - # behavior in postgres at least since 8.2). - order = 'user_id' - where.append('(user_id=%s or (user_id IS NULL)) order by '+ order) - params.append(uid) - clause = ' and '.join(where) - cr.execute('select id,name,value,object,meta, key from ir_values where ' + clause, params) - result = cr.fetchall() - if result: - break - - if not result: - return [] - - def _result_get(x, keys): - if x[1] in keys: - return False - keys.append(x[1]) - if x[3]: - model,id = x[2].split(',') - # FIXME: It might be a good idea to opt-in that kind of stuff - # FIXME: instead of arbitrarily removing random fields - fields = [ - field - for field in self.pool.get(model).fields_get_keys(cr, uid) - if field not in EXCLUDED_FIELDS] - - try: - datas = self.pool.get(model).read(cr, uid, [int(id)], fields, context) - except except_orm, e: - return False - datas = datas and datas[0] - if not datas: - return False - else: - datas = pickle.loads(x[2].encode('utf-8')) - if meta: - return (x[0], x[1], datas, pickle.loads(x[4])) - return (x[0], x[1], datas) - keys = [] - res = filter(None, map(lambda x: _result_get(x, keys), result)) - res2 = res[:] - for r in res: - if isinstance(r[2], dict) and r[2].get('type') in ('ir.actions.report.xml','ir.actions.act_window','ir.actions.wizard'): - groups = r[2].get('groups_id') - if groups: - cr.execute('SELECT COUNT(1) FROM res_groups_users_rel WHERE gid IN %s AND uid=%s',(tuple(groups), uid)) - cnt = cr.fetchone()[0] - if not cnt: - res2.remove(r) - if r[1] == 'Menuitem' and not res2: - raise osv.except_osv('Error !','You do not have the permission to perform this operation !!!') - return res2 -ir_values() + """ + assert key in ['default', 'action'], "ir.values entry keys must be in ['default','action']" + if key == 'default': + def do_get(model,res_id): + return self.get_defaults(cr, uid, model, condition=key2) + elif key == 'action': + def do_get(model,res_id): + return self.get_actions(cr, uid, action_slot=key2, model=model, res_id=res_id, context=context) + return self._map_legacy_model_list(models, do_get, merge_results=True) diff --git a/openerp/addons/base/module/module.py b/openerp/addons/base/module/module.py index 69db5fec4d3..3af8d290700 100644 --- a/openerp/addons/base/module/module.py +++ b/openerp/addons/base/module/module.py @@ -52,7 +52,7 @@ ACTION_DICT = { class module_category(osv.osv): _name = "ir.module.category" - _description = "Module Category" + _description = "Application" def _module_nbr(self,cr,uid, ids, prop, unknow_none, context): cr.execute('SELECT category_id, COUNT(*) \ @@ -70,29 +70,16 @@ class module_category(osv.osv): result.get(id, 0)) return result - def name_get(self, cr, uid, ids, context=None): - result = [] - - reads = self.read(cr, uid, ids, ['name', 'parent_id'], context=context) - for record in reads: - name = record['name'] - if record['parent_id']: - name = record['parent_id'][1] + ' / ' + name - result.append((record['id'], name,)) - - return result - _columns = { 'name': fields.char("Name", size=128, required=True, select=True), - 'parent_id': fields.many2one('ir.module.category', 'Parent Category', select=True), - 'child_ids': fields.one2many('ir.module.category', 'parent_id', 'Child Categories'), + 'parent_id': fields.many2one('ir.module.category', 'Parent Application', select=True), + 'child_ids': fields.one2many('ir.module.category', 'parent_id', 'Child Applications'), 'module_nr': fields.function(_module_nbr, method=True, string='Number of Modules', type='integer'), 'module_ids' : fields.one2many('ir.module.module', 'category_id', 'Modules'), 'description' : fields.text("Description"), 'sequence' : fields.integer('Sequence'), } _order = 'name' -module_category() class module(osv.osv): _name = "ir.module.module" @@ -233,6 +220,7 @@ class module(osv.osv): 'demo': False, 'license': 'AGPL-3', 'web': False, + 'complexity': 'normal', } _order = 'name' diff --git a/openerp/addons/base/res/res_currency.py b/openerp/addons/base/res/res_currency.py index 01d8b745e6f..4e7029d5172 100644 --- a/openerp/addons/base/res/res_currency.py +++ b/openerp/addons/base/res/res_currency.py @@ -18,6 +18,7 @@ # along with this program. If not, see . # ############################################################################## +import re import time import netsvc from osv import fields, osv @@ -26,6 +27,8 @@ import tools from tools.misc import currency from tools.translate import _ +CURRENCY_DISPLAY_PATTERN = re.compile(r'(\w+)\s*(?:\((.*)\))?') + class res_currency(osv.osv): def _current_rate(self, cr, uid, ids, name, arg, context=None): if context is None: @@ -68,6 +71,8 @@ class res_currency(osv.osv): _defaults = { 'active': lambda *a: 1, 'position' : 'after', + 'rounding': 0.01, + 'accuracy': 4, } _sql_constraints = [ # this constraint does not cover all cases due to SQL NULL handling for company_id, @@ -101,6 +106,18 @@ class res_currency(osv.osv): r['date'] = currency_date return res + def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100): + if not args: + args = [] + if name: + ids = self.search(cr, user, ([('name','=',name)] + args), limit=limit, context=context) + name_match = CURRENCY_DISPLAY_PATTERN.match(name) + if not ids and name_match: + ids = self.search(cr, user, [('name','=', name_match.group(1))] + args, limit=limit, context=context) + else: + ids = self.search(cr, user, args, limit=limit, context=context) + return self.name_get(cr, user, ids, context=context) + def name_get(self, cr, uid, ids, context=None): if not ids: return [] diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index 9746db40cc1..ae58c9a9f33 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -185,7 +185,7 @@ class res_partner(osv.osv): def name_get(self, cr, uid, ids, context={}): if not len(ids): return [] - if context.get('show_ref', False): + if context and context.get('show_ref'): rec_name = 'ref' else: rec_name = 'name' @@ -196,8 +196,6 @@ class res_partner(osv.osv): def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100): if not args: args=[] - if not context: - context={} if name: ids = self.search(cr, uid, [('ref', '=', name)] + args, limit=limit, context=context) if not ids: @@ -312,6 +310,8 @@ class res_partner_address(osv.osv): } def name_get(self, cr, user, ids, context={}): + if context is None: + context = {} if not len(ids): return [] res = [] diff --git a/openerp/addons/base/security/base_security.xml b/openerp/addons/base/security/base_security.xml index 59f891bd83d..65696210687 100644 --- a/openerp/addons/base/security/base_security.xml +++ b/openerp/addons/base/security/base_security.xml @@ -69,6 +69,13 @@ [('company_id','child_of',[user.company_id.id])] + + Defaults: alter personal values only + + [('key','=','default'),('user_id','=',user.id)] + + +
diff --git a/openerp/addons/base/security/ir.model.access.csv b/openerp/addons/base/security/ir.model.access.csv index 659938a73a3..7bb6e63f049 100644 --- a/openerp/addons/base/security/ir.model.access.csv +++ b/openerp/addons/base/security/ir.model.access.csv @@ -38,8 +38,7 @@ "access_ir_ui_view_custom_group_user","ir_ui_view_custom_group_user","model_ir_ui_view_custom",,1,0,0,0 "access_ir_ui_view_custom_group_system","ir_ui_view_custom_group_system","model_ir_ui_view_custom","group_system",1,1,1,1 "access_ir_ui_view_sc_group_user","ir_ui_view_sc group_user","model_ir_ui_view_sc",,1,1,1,1 -"access_ir_values_group_erp_manager","ir_values group_erp_manager","model_ir_values","group_erp_manager",1,1,1,1 -"access_ir_values_group_all","ir_values group_all","model_ir_values",,1,0,1,0 +"access_ir_values_group_all","ir_values group_all","model_ir_values",,1,1,1,1 "access_res_company_group_erp_manager","res_company group_erp_manager","model_res_company","group_erp_manager",1,1,1,1 "access_res_company_group_user","res_company group_user","model_res_company",,1,0,0,0 "access_res_country_group_all","res_country group_user_all","model_res_country",,1,0,0,0 diff --git a/openerp/addons/base/test/test_ir_values.yml b/openerp/addons/base/test/test_ir_values.yml index b4c08db31ff..a8912e0ad3b 100644 --- a/openerp/addons/base/test/test_ir_values.yml +++ b/openerp/addons/base/test/test_ir_values.yml @@ -2,24 +2,71 @@ Create some default value for some (non-existing) model, for all users. - !python {model: ir.values }: | - self.set(cr, uid, 'default', False, 'my_test_ir_value',['unexisting_model'], 'global value') + self.set(cr, uid, 'default', False, 'my_test_field',['unexisting_model'], 'global value') - Retrieve it. - !python {model: ir.values }: | # d is a list of triple (id, name, value) d = self.get(cr, uid, 'default', False, ['unexisting_model']) - assert d[0][1] == u'my_test_ir_value', "Can't retrieve the created default value." + assert d[0][1] == 'my_test_field', "Can't retrieve the created default value." assert d[0][2] == 'global value', "Can't retrieve the created default value." - Do it again but for a specific user. - !python {model: ir.values }: | - self.set(cr, uid, 'default', False, 'my_test_ir_value',['unexisting_model'], 'specific value', preserve_user=True) + self.set(cr, uid, 'default', False, 'my_test_field',['unexisting_model'], 'specific value', preserve_user=True) - Retrieve it and check it is the one for the current user. - !python {model: ir.values }: | d = self.get(cr, uid, 'default', False, ['unexisting_model']) - assert d[0][1] == u'my_test_ir_value', "Can't retrieve the created default value." + assert len(d) == 1, "Only one default must be returned per field" + assert d[0][1] == 'my_test_field', "Can't retrieve the created default value." assert d[0][2] == 'specific value', "Can't retrieve the created default value." +- + Create some action bindings for a non-existing model +- + !python {model: ir.values }: | + self.set(cr, uid, 'action', 'tree_but_open', 'OnDblClick Action', ['unexisting_model'], 'ir.actions.act_window,10', isobject=True) + self.set(cr, uid, 'action', 'tree_but_open', 'OnDblClick Action 2', ['unexisting_model'], 'ir.actions.act_window,11', isobject=True) + self.set(cr, uid, 'action', 'client_action_multi', 'Side Wizard', ['unexisting_model'], 'ir.actions.act_window,12', isobject=True) + self.set(cr, uid, 'action', 'client_print_multi', 'Nice Report', ['unexisting_model'], 'ir.actions.report.xml,2', isobject=True) + self.set(cr, uid, 'action', 'client_action_relate', 'Related Stuff', ['unexisting_model'], 'ir.actions.act_window,14', isobject=True) +- + Replace one action binding to set a new name +- + !python {model: ir.values }: | + self.set(cr, uid, 'action', 'tree_but_open', 'OnDblClick Action New', ['unexisting_model'], 'ir.actions.act_window,10', isobject=True) +- + Retrieve the action bindings and check they're correct +- + !python {model: ir.values }: | + actions = self.get(cr, uid, 'action', 'tree_but_open', ['unexisting_model']) + assert len(actions) == 2, "Mismatching number of bound actions" + #first action + assert len(actions[0]) == 3, "Malformed action definition" + assert actions[0][1] == 'OnDblClick Action 2', 'Bound action does not match definition' + assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == 11, 'Bound action does not match definition' + #second action - this ones comes last because it was re-created with a different name + assert len(actions[1]) == 3, "Malformed action definition" + assert actions[1][1] == 'OnDblClick Action New', 'Re-Registering an action should replace it' + assert isinstance(actions[1][2], dict) and actions[1][2]['id'] == 10, 'Bound action does not match definition' + + actions = self.get(cr, uid, 'action', 'client_action_multi', ['unexisting_model']) + assert len(actions) == 1, "Mismatching number of bound actions" + assert len(actions[0]) == 3, "Malformed action definition" + assert actions[0][1] == 'Side Wizard', 'Bound action does not match definition' + assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == 12, 'Bound action does not match definition' + + actions = self.get(cr, uid, 'action', 'client_print_multi', ['unexisting_model']) + assert len(actions) == 1, "Mismatching number of bound actions" + assert len(actions[0]) == 3, "Malformed action definition" + assert actions[0][1] == 'Nice Report', 'Bound action does not match definition' + assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == 2, 'Bound action does not match definition' + + actions = self.get(cr, uid, 'action', 'client_action_relate', ['unexisting_model']) + assert len(actions) == 1, "Mismatching number of bound actions" + assert len(actions[0]) == 3, "Malformed action definition" + assert actions[0][1] == 'Related Stuff', 'Bound action does not match definition' + assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == 14, 'Bound action does not match definition' diff --git a/openerp/modules/module.py b/openerp/modules/module.py index 809339897ab..53e8b86d6bf 100644 --- a/openerp/modules/module.py +++ b/openerp/modules/module.py @@ -249,7 +249,7 @@ def load_information_from_description_file(module): info.setdefault('website', '') info.setdefault('name', False) info.setdefault('description', '') - info.setdefault('complexity', False) + info.setdefault('complexity', 'normal') info['certificate'] = info.get('certificate') or None info['web'] = info.get('web') or False info['license'] = info.get('license') or 'AGPL-3' diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index 0d60a1cbb95..6cc5255faa7 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -156,9 +156,21 @@ class integer_big(_column): class reference(_column): _type = 'reference' + _classic_read = False # post-process to handle missing target + def __init__(self, string, selection, size, **args): _column.__init__(self, string=string, size=size, selection=selection, **args) + def get(self, cr, obj, ids, name, uid=None, context=None, values=None): + result = {} + # copy initial values fetched previously. + for value in values: + result[value['id']] = value[name] + if value[name]: + model, res_id = value[name].split(',') + if not obj.pool.get(model).exists(cr, uid, [int(res_id)], context=context): + result[value['id']] = False + return result class char(_column): _type = 'char' @@ -495,6 +507,15 @@ class one2many(_column): class many2many(_column): """Encapsulates the logic of a many-to-many bidirectional relationship, handling the low-level details of the intermediary relationship table transparently. + A many-to-many relationship is always symmetrical, and can be declared and accessed + from either endpoint model. + If ``rel`` (relationship table name), ``id1`` (source foreign key column name) + or id2 (destination foreign key column name) are not specified, the system will + provide default values. This will by default only allow one single symmetrical + many-to-many relationship between the source and destination model. + For multiple many-to-many relationship between the same models and for + relationships where source and destination models are the same, ``rel``, ``id1`` + and ``id2`` should be specified explicitly. :param str obj: destination model :param str rel: optional name of the intermediary relationship table. If not specified, diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 2a8553e122b..5bafd964a61 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -44,6 +44,7 @@ import calendar import copy import datetime +import itertools import logging import operator import pickle @@ -303,7 +304,8 @@ class browse_record(object): self._cr = cr self._uid = uid self._id = id - self._table = table + self._table = table # deprecated, use _model! + self._model = table self._table_name = self._table._name self.__logger = logging.getLogger( 'osv.browse_record.' + self._table_name) @@ -460,6 +462,9 @@ class browse_record(object): def __contains__(self, name): return (name in self._table._columns) or (name in self._table._inherit_fields) or hasattr(self._table, name) + def __iter__(self): + raise NotImplementedError("Iteration is not allowed on %s" % self) + def __hasattr__(self, name): return name in self @@ -815,13 +820,16 @@ class BaseModel(object): raise TypeError('_name is mandatory in case of multiple inheritance') for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]): - parent_class = pool.get(parent_name).__class__ - if not pool.get(parent_name): + parent_model = pool.get(parent_name) + if not getattr(cls, '_original_module', None) and name == parent_model._name: + cls._original_module = parent_model._original_module + if not parent_model: raise TypeError('The model "%s" specifies an unexisting parent class "%s"\n' 'You may need to add a dependency on the parent class\' module.' % (name, parent_name)) + parent_class = parent_model.__class__ nattr = {} for s in attributes: - new = copy.copy(getattr(pool.get(parent_name), s, {})) + new = copy.copy(getattr(parent_model, s, {})) if s == '_columns': # Don't _inherit custom fields. for c in new.keys(): @@ -849,6 +857,8 @@ class BaseModel(object): new.extend(cls.__dict__.get(s, [])) nattr[s] = new cls = type(name, (cls, parent_class), dict(nattr, _register=False)) + if not getattr(cls, '_original_module', None): + cls._original_module = cls._module obj = object.__new__(cls) obj.__init__(pool, cr) return obj @@ -912,14 +922,17 @@ class BaseModel(object): f = self._columns[store_field] if hasattr(f, 'digits_change'): f.digits_change(cr) + def not_this_field(stored_func): + x, y, z, e, f, l = stored_func + return x != self._name or y != store_field + self.pool._store_function[self._name] = filter(not_this_field, self.pool._store_function.get(self._name, [])) if not isinstance(f, fields.function): continue if not f.store: continue - if self._columns[store_field].store is True: + sm = f.store + if sm is True: sm = {self._name: (lambda self, cr, uid, ids, c={}: ids, None, 10, None)} - else: - sm = self._columns[store_field].store for object, aa in sm.items(): if len(aa) == 4: (fnct, fields2, order, length) = aa @@ -930,14 +943,8 @@ class BaseModel(object): raise except_orm('Error', ('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority, time length)}.' % (store_field, self._name))) self.pool._store_function.setdefault(object, []) - ok = True - for x, y, z, e, f, l in self.pool._store_function[object]: - if (x==self._name) and (y==store_field) and (e==fields2): - if f == order: - ok = False - if ok: - self.pool._store_function[object].append( (self._name, store_field, fnct, tuple(fields2) if fields2 else None, order, length)) - self.pool._store_function[object].sort(lambda x, y: cmp(x[4], y[4])) + self.pool._store_function[object].append((self._name, store_field, fnct, tuple(fields2) if fields2 else None, order, length)) + self.pool._store_function[object].sort(lambda x, y: cmp(x[4], y[4])) for (key, _, msg) in self._sql_constraints: self.pool._sql_error[self._table+'_'+key] = msg @@ -1047,6 +1054,7 @@ class BaseModel(object): 'name': n, 'model': self._name, 'res_id': r['id'], + 'module': '__export__', }) r = n else: @@ -1730,13 +1738,69 @@ class BaseModel(object): raise except_orm('View error', msg) return arch, fields - def __get_default_calendar_view(self): - """Generate a default calendar view (For internal use only). - """ - # TODO could return an etree instead of a string + def _get_default_form_view(self, cr, user, context=None): + """ Generates a default single-line form view using all fields + of the current model except the m2m and o2m ones. - arch = ('\n' - '\n' - ' \n' - '') % (self._rec_name) - - return arch - - def __get_default_search_view(self, cr, uid, context=None): + def _get_default_search_view(self, cr, uid, context=None): + """ + :param cr: database cursor + :param int user: user id + :param dict context: connection context + :returns: an lxml document of the view + :rtype: etree._Element + """ form_view = self.fields_view_get(cr, uid, False, 'form', context=context) tree_view = self.fields_view_get(cr, uid, False, 'tree', context=context) - fields_to_search = set() # TODO it seems _all_columns could be used instead of fields_get (no need for translated fields info) fields = self.fields_get(cr, uid, context=context) - for field in fields: - if fields[field].get('select'): - fields_to_search.add(field) + fields_to_search = set( + field for field, descriptor in fields.iteritems() + if descriptor.get('select')) + for view in (form_view, tree_view): view_root = etree.fromstring(view['arch']) # Only care about select=1 in xpath below, because select=2 is covered # by the custom advanced search in clients - fields_to_search = fields_to_search.union(view_root.xpath("//field[@select=1]/@name")) + fields_to_search.update(view_root.xpath("//field[@select=1]/@name")) tree_view_root = view_root # as provided by loop above - search_view = etree.Element("search", attrib={'string': tree_view_root.get("string", "")}) - field_group = etree.Element("group") - search_view.append(field_group) + search_view = etree.Element("search", string=tree_view_root.get("string", "")) + field_group = etree.SubElement(search_view, "group") for field_name in fields_to_search: - field_group.append(etree.Element("field", attrib={'name': field_name})) + etree.SubElement(field_group, "field", name=field_name) - #TODO tostring can be removed as fromstring is call directly after... - return etree.tostring(search_view, encoding="utf-8").replace('\t', '') + return search_view # # if view_id, view_type is not required @@ -1993,50 +2048,27 @@ class BaseModel(object): # if a view was found if sql_res: - result['type'] = sql_res['type'] - result['view_id'] = sql_res['id'] - source = etree.fromstring(encode(sql_res['arch'])) - result['arch'] = apply_view_inheritance(cr, user, source, result['view_id']) - - result['name'] = sql_res['name'] - result['field_parent'] = sql_res['field_parent'] or False + result.update( + arch=apply_view_inheritance(cr, user, source, sql_res['id']), + type=sql_res['type'], + view_id=sql_res['id'], + name=sql_res['name'], + field_parent=sql_res['field_parent'] or False) else: - # otherwise, build some kind of default view - if view_type == 'form': - # TODO it seems fields_get can be replaced by _all_columns (no need for translation) - res = self.fields_get(cr, user, context=context) - xml = ' ' \ - '
' % (self._description,) - for x in res: - if res[x]['type'] not in ('one2many', 'many2many'): - xml += '' % (x,) - if res[x]['type'] == 'text': - xml += "" - xml += "" - - elif view_type == 'tree': - _rec_name = self._rec_name - if _rec_name not in self._columns: - _rec_name = self._columns.keys()[0] - xml = '' \ - '' \ - % (self._description, _rec_name) - - elif view_type == 'calendar': - xml = self.__get_default_calendar_view() - - elif view_type == 'search': - xml = self.__get_default_search_view(cr, user, context) - - else: + try: + view = getattr(self, '_get_default_%s_view' % view_type)( + cr, user, context) + except AttributeError: # what happens here, graph case? raise except_orm(_('Invalid Architecture!'), _("There is no view of type '%s' defined for the structure!") % view_type) - result['arch'] = etree.fromstring(encode(xml)) - result['name'] = 'default' - result['field_parent'] = False - result['view_id'] = 0 + + result.update( + arch=view, + name='default', + field_parent=False, + view_id=0) if parent_view_model != self._name: ctx = context.copy() @@ -2067,14 +2099,13 @@ class BaseModel(object): resrelate = ir_values_obj.get(cr, user, 'action', 'client_action_relate', [(self._name, False)], False, context) - resprint = map(clean, resprint) - resaction = map(clean, resaction) - if view_type != 'tree': - resaction = filter(lambda x: not x.get('multi'), resaction) - resprint = filter(lambda x: not x.get('multi'), resprint) + resaction = [clean(action) for action in resaction + if view_type == 'tree' or not action[2].get('multi')] + resprint = [clean(print_) for print_ in resprint + if view_type == 'tree' or not print_[2].get('multi')] resrelate = map(lambda x: x[2], resrelate) - for x in resprint + resaction + resrelate: + for x in itertools.chain(resprint, resaction, resrelate): x['string'] = x['name'] result['toolbar'] = { @@ -3459,7 +3490,7 @@ class BaseModel(object): if uid == SUPERUSER_ID: return - if self.is_transient: + if self.is_transient(): # Only one single implicit access rule for transient models: owner only! # This is ok to hardcode because we assert that TransientModels always # have log_access enabled and this the create_uid column is always there. @@ -4591,17 +4622,21 @@ class BaseModel(object): return False return True - def _get_xml_ids(self, cr, uid, ids, *args, **kwargs): - """Find out the XML ID(s) of any database record. + def _get_external_ids(self, cr, uid, ids, *args, **kwargs): + """Retrieve the External ID(s) of any database record. **Synopsis**: ``_get_xml_ids(cr, uid, ids) -> { 'id': ['module.xml_id'] }`` - :return: map of ids to the list of their fully qualified XML IDs - (empty list when there's none). + :return: map of ids to the list of their fully qualified External IDs + in the form ``module.key``, or an empty list when there's no External + ID for a record, e.g.:: + + { 'id': ['module.ext_id', 'module.ext_id_bis'], + 'id2': [] } """ - model_data_obj = self.pool.get('ir.model.data') - data_ids = model_data_obj.search(cr, uid, [('model', '=', self._name), ('res_id', 'in', ids)]) - data_results = model_data_obj.read(cr, uid, data_ids, ['module', 'name', 'res_id']) + ir_model_data = self.pool.get('ir.model.data') + data_ids = ir_model_data.search(cr, uid, [('model', '=', self._name), ('res_id', 'in', ids)]) + data_results = ir_model_data.read(cr, uid, data_ids, ['module', 'name', 'res_id']) result = {} for id in ids: # can't use dict.fromkeys() as the list would be shared! @@ -4610,29 +4645,35 @@ class BaseModel(object): result[record['res_id']].append('%(module)s.%(name)s' % record) return result - def get_xml_id(self, cr, uid, ids, *args, **kwargs): - """Find out the XML ID of any database record, if there + def get_external_id(self, cr, uid, ids, *args, **kwargs): + """Retrieve the External ID of any database record, if there is one. This method works as a possible implementation for a function field, to be able to add it to any - model object easily, referencing it as ``osv.osv.get_xml_id``. + model object easily, referencing it as ``Model.get_external_id``. - When multiple XML IDs exist for a record, only one + When multiple External IDs exist for a record, only one of them is returned (randomly). - **Synopsis**: ``get_xml_id(cr, uid, ids) -> { 'id': 'module.xml_id' }`` - :return: map of ids to their fully qualified XML ID, defaulting to an empty string when there's none - (to be usable as a function field). + (to be usable as a function field), + e.g.:: + + { 'id': 'module.ext_id', + 'id2': '' } """ results = self._get_xml_ids(cr, uid, ids) - for k, v in results.items(): + for k, v in results.iteritems(): if results[k]: results[k] = v[0] else: results[k] = '' return results + # backwards compatibility + get_xml_id = get_external_id + _get_xml_ids = _get_external_ids + # Transience def is_transient(self): """ Return whether the model is transient. diff --git a/openerp/release.py b/openerp/release.py index 23a764bc94c..babe0ca4806 100644 --- a/openerp/release.py +++ b/openerp/release.py @@ -36,6 +36,5 @@ url = 'http://www.openerp.com' author = 'OpenERP S.A.' author_email = 'info@openerp.com' license = 'AGPL-3' -timestamp = None # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/tools/safe_eval.py b/openerp/tools/safe_eval.py index be7b222f67f..02fcbc19c63 100644 --- a/openerp/tools/safe_eval.py +++ b/openerp/tools/safe_eval.py @@ -64,7 +64,7 @@ _SAFE_OPCODES = _EXPR_OPCODES.union(set(opmap[x] for x in [ 'MAKE_FUNCTION', 'SLICE+0', 'SLICE+1', 'SLICE+2', 'SLICE+3', # New in Python 2.7 - http://bugs.python.org/issue4715 : 'JUMP_IF_FALSE_OR_POP', 'JUMP_IF_TRUE_OR_POP', 'POP_JUMP_IF_FALSE', - 'POP_JUMP_IF_TRUE' + 'POP_JUMP_IF_TRUE', 'SETUP_EXCEPT', 'END_FINALLY' ] if x in opmap)) _logger = logging.getLogger('safe_eval') diff --git a/openerp/tools/yaml_import.py b/openerp/tools/yaml_import.py index bb4ce9db87d..91cdda1b896 100644 --- a/openerp/tools/yaml_import.py +++ b/openerp/tools/yaml_import.py @@ -340,6 +340,7 @@ class YamlInterpreter(object): return record_dict def process_ref(self, node, column=None): + assert node.search or node.id, '!ref node should have a `search` attribute or `id` attribute' if node.search: if node.model: model_name = node.model @@ -377,7 +378,10 @@ class YamlInterpreter(object): if column._type in ("many2many", "one2many"): value = [(6, 0, elements)] else: # many2one - value = self._get_first_result(elements) + if isinstance(elements, (list,tuple)): + value = self._get_first_result(elements) + else: + value = elements elif column._type == "many2one": value = self.get_id(expression) elif column._type == "one2many": diff --git a/openerp/workflow/wkf_expr.py b/openerp/workflow/wkf_expr.py index 468e5225212..608af34d118 100644 --- a/openerp/workflow/wkf_expr.py +++ b/openerp/workflow/wkf_expr.py @@ -60,7 +60,7 @@ def _eval_expr(cr, ident, workitem, action): def execute_action(cr, ident, workitem, activity): obj = pooler.get_pool(cr.dbname).get('ir.actions.server') - ctx = {'active_id':ident[2], 'active_ids':[ident[2]]} + ctx = {'active_model':ident[1], 'active_id':ident[2], 'active_ids':[ident[2]]} result = obj.run(cr, ident[0], [activity['action_id']], ctx) return result diff --git a/setup.py b/setup.py index 08c30faf737..9aabfb0a344 100755 --- a/setup.py +++ b/setup.py @@ -63,8 +63,6 @@ def py2exe_options(): return {} execfile(join(os.path.dirname(__file__), 'openerp', 'release.py')) -if timestamp: - version = version + "-" + timestamp setuptools.setup( name = 'openerp',