diff --git a/openerp/addons/base/base_data.xml b/openerp/addons/base/base_data.xml index 2503adc5525..a0e66de8dba 100644 --- a/openerp/addons/base/base_data.xml +++ b/openerp/addons/base/base_data.xml @@ -1373,7 +1373,7 @@ INR - Rs + ₹ 0.01 4 diff --git a/openerp/addons/base/i18n/ja.po b/openerp/addons/base/i18n/ja.po index 31643b45dd5..3380fef7b6c 100644 --- a/openerp/addons/base/i18n/ja.po +++ b/openerp/addons/base/i18n/ja.po @@ -8,14 +8,14 @@ msgstr "" "Project-Id-Version: openobject-server\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2012-02-08 00:44+0000\n" -"PO-Revision-Date: 2012-02-28 16:02+0000\n" +"PO-Revision-Date: 2012-03-21 21:49+0000\n" "Last-Translator: Akira Hiyama \n" "Language-Team: Japanese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-03-10 04:49+0000\n" -"X-Generator: Launchpad (build 14914)\n" +"X-Launchpad-Export-Date: 2012-03-22 04:56+0000\n" +"X-Generator: Launchpad (build 14981)\n" #. module: base #: model:res.country,name:base.sh @@ -8310,11 +8310,14 @@ msgid "" "interface, which has less features but is easier to use. You can switch to " "the other interface from the User/Preferences menu at any time." msgstr "" +"OpenERPは簡易 / " +"拡張の2種類のユーザインタフェースを提供します。最初にOpenERPを使う場合は,機能は少ないながらも使い易い簡易なユーザインタフェースを選択することを強" +"くお勧めします。なお,いつでも設定メニューから拡張インタフェースに変更できます。" #. module: base #: model:res.country,name:base.cc msgid "Cocos (Keeling) Islands" -msgstr "" +msgstr "ココス諸島" #. module: base #: selection:base.language.install,state:0 @@ -8326,22 +8329,22 @@ msgstr "" #. module: base #: view:res.lang:0 msgid "11. %U or %W ==> 48 (49th week)" -msgstr "" +msgstr "11. %U or %W ==> 48(49番目の週)" #. module: base #: model:ir.model,name:base.model_res_partner_bank_type_field msgid "Bank type fields" -msgstr "" +msgstr "銀行タイプ項目" #. module: base #: selection:base.language.install,lang:0 msgid "Dutch / Nederlands" -msgstr "" +msgstr "オランダ" #. module: base #: selection:res.company,paper_format:0 msgid "US Letter" -msgstr "" +msgstr "US レター" #. module: base #: model:ir.module.module,description:base.module_stock_location @@ -8433,6 +8436,63 @@ msgid "" "from Gate A\n" " " msgstr "" +"\n" +"このモジュールはプッシュ型プル型倉庫フローを効果的に実行する倉庫アプリケーションを補います。\n" +"=============================================================================" +"===============================\n" +"\n" +"典型的な利用例:\n" +" * 製品製造チェーンの管理\n" +" * 製品ごとのデフォルトロケーションの管理\n" +" * 以下のようなビジネス要求による倉庫の中の経路の定義:\n" +" - 品質管理\n" +" - 販売サービスの後\n" +" - サプライヤ返品\n" +"\n" +" * レンタル製品の自動返品を生成することでレンタル管理を助けます。\n" +"\n" +"ひとたびこのモジュールがインストールされたら,製品フォームに追加タブが現れます。\n" +"そこで,プッシュとプルのフローの仕様を加えることができます。\n" +"CPU1製品のプッシュとプルのデモデータ:\n" +"\n" +"プッシュフロー\n" +"----------\n" +"プッシュフローは,一定の製品が常に必要に応じて他の場所に対応して動く到着する時,\n" +"オプションとして一定の遅延がある時に役立ちます。\n" +"オリジナルの倉庫アプリケーションは既にそれ自身にプッシュフローの仕様をサポートしています。\n" +"しかし,これは製品ごとに改良されません。\n" +"\n" +"プッシュプローの仕様はどの場所がどの場所と何のパラメータでつながれているかを示します。\n" +"製品の所定の量が供給元の場所で動きがあるや否や結び付けられた動作がフロー定義\n" +"(目的地の場所,遅延,移動の種類,ジャーナル他)でセットされたパラメータにしたがって\n" +"自動的に予測されます。\n" +"この新しい動きはパラメータにしたがって自動的に実行されるか,手動確認を要求します。\n" +"\n" +"プルフロー\n" +"----------\n" +"プルフローはプッシュフローとは少し異なっています。これは製品の動きの処理には関係せず,\n" +"むしろ獲得した注文の処理に関係します。直接の商品ではなくニーズに依ります。\n" +"プルフローの古典的な例は,供給に責任を持つ親会社を持つアウトレット会社です。\n" +"\n" +" [ 顧客 ] <- A - [ アウトレット ] <- B - [ 親会社 ] <~ C ~ [ 供給者 ]\n" +"\n" +"新しい獲得注文(A:例として注文の確認が来ます)がアウトレットに到着すると,それは親会社から要求されたもう一つの獲得注文に変換されます(B:プルフローのタ" +"イプは'move')。親会社により獲得注文Bが処理される時,そしてその製品が在庫切れの時,それは供給者からの受注(C:プルフローのタイプは購入)に変換され" +"ます。結果として獲得注文,ニーズは顧客と供給者の間の全てにプッシュされる。\n" +"\n" +"専門的には,プルフローは獲得注文と別に処理することを許します。製品に対しての考慮に依存するのみならず,その製品のニーズを持つ場所(即ち獲得注文の目的地)に" +"依存します。\n" +"\n" +"使用例\n" +"--------\n" +"\n" +"以下のデモデータを使います:\n" +" CPU1:ショップ1からいくらかのCPU1を売り,スケジューラを走らせます。\n" +" - 倉庫:配達注文,ショップ1: 受付\n" +" CPU3:\n" +" - 商品を受け取る時,それは品質管理場所に行き,棚2に保管されます。\n" +" - 顧客に配達する時:ピックリスト -> 梱包 -> GateAから配達注文\n" +" " #. module: base #: model:ir.module.module,description:base.module_marketing @@ -8444,11 +8504,17 @@ msgid "" "Contains the installer for marketing-related modules.\n" " " msgstr "" +"\n" +"マーケティングメニュー\n" +"===================\n" +"\n" +"マーケティング関連のモジュールのインストーラを含んでいます。\n" +" " #. module: base #: model:ir.module.category,name:base.module_category_knowledge_management msgid "Knowledge Management" -msgstr "" +msgstr "知識管理" #. module: base #: model:ir.actions.act_window,name:base.bank_account_update diff --git a/openerp/addons/base/ir/ir_mail_server.py b/openerp/addons/base/ir/ir_mail_server.py index ecb41b32592..04156411e48 100644 --- a/openerp/addons/base/ir/ir_mail_server.py +++ b/openerp/addons/base/ir/ir_mail_server.py @@ -22,6 +22,7 @@ from email.MIMEText import MIMEText from email.MIMEBase import MIMEBase from email.MIMEMultipart import MIMEMultipart +from email.Charset import Charset from email.Header import Header from email.Utils import formatdate, make_msgid, COMMASPACE from email import Encoders @@ -97,6 +98,26 @@ def encode_header(header_text): return header_text_ascii if header_text_ascii\ else Header(header_text_utf8, 'utf-8') +def encode_header_param(param_text): + """Returns an appropriate RFC2047 encoded representation of the given + header parameter value, suitable for direct assignation as the + param value (e.g. via Message.set_param() or Message.add_header()) + RFC2822 assumes that headers contain only 7-bit characters, + so we ensure it is the case, using RFC2047 encoding when needed. + + :param param_text: unicode or utf-8 encoded string with header value + :rtype: string + :return: if ``param_text`` represents a plain ASCII string, + return the same 7-bit string, otherwise returns an + ASCII string containing the RFC2047 encoded text. + """ + # For details see the encode_header() method that uses the same logic + if not param_text: return "" + param_text_utf8 = tools.ustr(param_text).encode('utf-8') + param_text_ascii = try_coerce_ascii(param_text_utf8) + return param_text_ascii if param_text_ascii\ + else Charset('utf8').header_encode(param_text_utf8) + name_with_email_pattern = re.compile(r'("[^<@>]+")\s*<([^ ,<@]+@[^> ,]+)>') address_pattern = re.compile(r'([^ ,<@]+@[^> ,]+)') @@ -309,7 +330,7 @@ class ir_mail_server(osv.osv): msg['Cc'] = encode_rfc2822_address_header(COMMASPACE.join(email_cc)) if email_bcc: msg['Bcc'] = encode_rfc2822_address_header(COMMASPACE.join(email_bcc)) - msg['Date'] = formatdate(localtime=True) + msg['Date'] = formatdate() # Custom headers may override normal headers or provide additional ones for key, value in headers.iteritems(): msg[ustr(key).encode('utf-8')] = encode_header(value) @@ -334,14 +355,16 @@ class ir_mail_server(osv.osv): if attachments: for (fname, fcontent) in attachments: - filename_utf8 = ustr(fname).encode('utf-8') + filename_rfc2047 = encode_header_param(fname) part = MIMEBase('application', "octet-stream") + + # The default RFC2231 encoding of Message.add_header() works in Thunderbird but not GMail + # so we fix it by using RFC2047 encoding for the filename instead. + part.set_param('name', filename_rfc2047) + part.add_header('Content-Disposition', 'attachment', filename=filename_rfc2047) + part.set_payload(fcontent) Encoders.encode_base64(part) - # Force RFC2231 encoding for attachment filename - # See email.message.Message.add_header doc - part.add_header('Content-Disposition', 'attachment', - filename=('utf-8',None,filename_utf8)) msg.attach(part) return msg diff --git a/openerp/addons/base/ir/ir_values.py b/openerp/addons/base/ir/ir_values.py index bd619966b67..2a33e0c73c4 100644 --- a/openerp/addons/base/ir/ir_values.py +++ b/openerp/addons/base/ir/ir_values.py @@ -404,7 +404,7 @@ class ir_values(osv.osv): results[action['name']] = (action['id'], action['name'], action_def) except except_orm, e: continue - return results.values() + return sorted(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 diff --git a/openerp/addons/base/module/module.py b/openerp/addons/base/module/module.py index 5ca54d52c91..66762662377 100644 --- a/openerp/addons/base/module/module.py +++ b/openerp/addons/base/module/module.py @@ -466,6 +466,7 @@ class module(osv.osv): 'sequence': terp.get('sequence', 100), 'application': terp.get('application', False), 'auto_install': terp.get('auto_install', False), + 'icon': terp.get('icon', False), } # update the list of available packages diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index f4a36b46b77..9c98521e86c 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -346,21 +346,35 @@ class res_partner_address(osv.osv): def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100): if not args: - args=[] - if not context: - context={} - if context.get('contact_display', 'contact')=='partner ' or context.get('contact_display', 'contact')=='partner_address ' : - ids = self.search(cr, user, [('partner_id',operator,name)], limit=limit, context=context) + args = [] + if context is None: + context = {} + + if not name: + ids = self.search(cr, user, args, limit=limit, context=context) + elif context.get('contact_display', 'contact') == 'partner': + ids = self.search(cr, user, [('partner_id', operator, name)] + args, limit=limit, context=context) else: - if not name: - ids = self.search(cr, user, args, limit=limit, context=context) + # first lookup zip code, as it is a common and efficient way to search on these data + ids = self.search(cr, user, [('zip', '=', name)] + args, limit=limit, context=context) + # then search on other fields: + if context.get('contact_display', 'contact') == 'partner_address': + fields = ['partner_id', 'name', 'country_id', 'city', 'street'] else: - ids = self.search(cr, user, [('zip','=',name)] + args, limit=limit, context=context) - if not ids: - ids = self.search(cr, user, [('city',operator,name)] + args, limit=limit, context=context) - if name: - ids += self.search(cr, user, [('name',operator,name)] + args, limit=limit, context=context) - ids += self.search(cr, user, [('partner_id',operator,name)] + args, limit=limit, context=context) + fields = ['name', 'country_id', 'city', 'street'] + # Here we have to search the records that satisfy the domain: + # OR([[(f, operator, name)] for f in fields])) + args + # Searching on such a domain can be dramatically inefficient, due to the expansion made + # for field translations, and the handling of the disjunction by the DB engine itself. + # So instead, we search field by field until the search limit is reached. + while len(ids) < limit and fields: + f = fields.pop(0) + new_ids = self.search(cr, user, [(f, operator, name)] + args, limit=limit, context=context) + # extend ids with the ones in new_ids that are not in ids yet (and keep order) + old_ids = set(ids) + ids.extend([id for id in new_ids if id not in old_ids]) + + ids = ids[:limit] return self.name_get(cr, user, ids, context=context) def get_city(self, cr, uid, id): diff --git a/openerp/addons/base/res/res_security.xml b/openerp/addons/base/res/res_security.xml index 6783e316a6c..6a2bf52d21b 100644 --- a/openerp/addons/base/res/res_security.xml +++ b/openerp/addons/base/res/res_security.xml @@ -35,8 +35,8 @@ user rule - ['|',('company_id.child_ids','child_of',[user.company_id.id]),('company_id','child_of',[user.company_id.id])] + [('company_ids','child_of',[user.company_id.id])] - + diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index e7264b10d79..91941aa955c 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -34,6 +34,9 @@ from service import security from tools.translate import _ import openerp import openerp.exceptions +from lxml import etree +from lxml.builder import E + _logger = logging.getLogger(__name__) @@ -728,29 +731,27 @@ class groups_view(osv.osv): # and introduces the reified group fields view = self.get_user_groups_view(cr, uid, context) if view: - xml = u""" - - -%s -%s - -""" + xml1, xml2 = [], [] - xml1.append('' % _('Applications')) + xml1.append(E.separator(string=_('Application'), colspan="4")) for app, kind, gs in self.get_groups_by_application(cr, uid, context): if kind == 'selection': # application name with a selection field field_name = name_selection_groups(map(int, gs)) - xml1.append('' % field_name) - xml1.append('') + xml1.append(E.field(name=field_name)) + xml1.append(E.newline()) else: # application separator with boolean fields app_name = app and app.name or _('Other') - xml2.append('' % app_name) + xml2.append(E.separator(string=app_name, colspan="4")) for g in gs: field_name = name_boolean_group(g.id) - xml2.append('' % field_name) - view.write({'arch': xml % ('\n'.join(xml1), '\n'.join(xml2))}) + xml2.append(E.field(name=field_name)) + + xml = E.field(*(xml1 + xml2), name="groups_id", position="replace") + xml.addprevious(etree.Comment("GENERATED AUTOMATICALLY BY GROUPS")) + xml_content = etree.tostring(xml, pretty_print=True, xml_declaration=True, encoding="utf-8") + view.write({'arch': xml_content}) return True def get_user_groups_view(self, cr, uid, context=None): diff --git a/openerp/service/web_services.py b/openerp/service/web_services.py index 46beaecd323..330bb86dcfc 100644 --- a/openerp/service/web_services.py +++ b/openerp/service/web_services.py @@ -173,6 +173,8 @@ class db(netsvc.ExportService): raise Exception, e def exp_drop(self, db_name): + if not self.exp_db_exist(db_name): + return False openerp.modules.registry.RegistryManager.delete(db_name) sql_db.close_db(db_name) @@ -180,6 +182,17 @@ class db(netsvc.ExportService): cr = db.cursor() cr.autocommit(True) # avoid transaction block try: + # Try to terminate all other connections that might prevent + # dropping the database + try: + cr.execute("""SELECT pg_terminate_backend(procpid) + FROM pg_stat_activity + WHERE datname = %s AND + procpid != pg_backend_pid()""", + (db_name,)) + except Exception: + pass + try: cr.execute('DROP DATABASE "%s"' % db_name) except Exception, e: diff --git a/openerp/tools/graph.py b/openerp/tools/graph.py index b54e101eec4..1b039ce97c6 100755 --- a/openerp/tools/graph.py +++ b/openerp/tools/graph.py @@ -313,7 +313,8 @@ class graph(object): self.order[level] = self.order[level]+1 for sec_end in self.transitions.get(node, []): - self.init_order(sec_end, self.result[sec_end]['x']) + if node!=sec_end: + self.init_order(sec_end, self.result[sec_end]['x']) def order_heuristic(self): @@ -438,33 +439,27 @@ class graph(object): l.reverse() no = len(l) - if no%2==0: - first_half = l[no/2:] - factor = 1 - else: - first_half = l[no/2+1:] - factor = 0 - + rest = no%2 + first_half = l[no/2+rest:] last_half = l[:no/2] - i=1 - for child in first_half: - self.result[child]['y'] = mid_pos - (i - (factor * 0.5)) - i += 1 + for i, child in enumerate(first_half): + self.result[child]['y'] = mid_pos - (i+1 - (0 if rest else 0.5)) if self.transitions.get(child, False): if last: self.result[child]['y'] = last + len(self.transitions[child])/2 + 1 last = self.tree_order(child, last) - if no%2: + if rest: mid_node = l[no/2] self.result[mid_node]['y'] = mid_pos if self.transitions.get((mid_node), False): if last: self.result[mid_node]['y'] = last + len(self.transitions[mid_node])/2 + 1 - last = self.tree_order(mid_node) + if node!=mid_node: + last = self.tree_order(mid_node) else: if last: self.result[mid_node]['y'] = last + 1 @@ -474,13 +469,14 @@ class graph(object): i=1 last_child = None for child in last_half: - self.result[child]['y'] = mid_pos + (i - (factor * 0.5)) + self.result[child]['y'] = mid_pos + (i - (0 if rest else 0.5)) last_child = child i += 1 if self.transitions.get(child, False): if last: self.result[child]['y'] = last + len(self.transitions[child])/2 + 1 - last = self.tree_order(child, last) + if node!=child: + last = self.tree_order(child, last) if last_child: last = self.result[last_child]['y']