diff --git a/addons/auth_oauth/controllers/main.py b/addons/auth_oauth/controllers/main.py index 7652420d7c6..1a166c39cde 100644 --- a/addons/auth_oauth/controllers/main.py +++ b/addons/auth_oauth/controllers/main.py @@ -49,7 +49,8 @@ class OAuthLogin(Home): def list_providers(self): try: provider_obj = request.registry.get('auth.oauth.provider') - providers = provider_obj.search_read(request.cr, SUPERUSER_ID, [('enabled', '=', True)]) + providers = provider_obj.search_read(request.cr, SUPERUSER_ID, [('enabled', '=', True), ('auth_endpoint', '!=', False), ('validation_endpoint', '!=', False)]) + # TODO in forwardport: remove conditions on 'auth_endpoint' and 'validation_endpoint' when these fields will be 'required' in model except Exception: providers = [] for provider in providers: diff --git a/addons/auth_signup/res_users.py b/addons/auth_signup/res_users.py index 5a744c21c81..576a778be45 100644 --- a/addons/auth_signup/res_users.py +++ b/addons/auth_signup/res_users.py @@ -201,6 +201,14 @@ class res_users(osv.Model): partner.write({'signup_token': False, 'signup_type': False, 'signup_expiration': False}) partner_user = partner.user_ids and partner.user_ids[0] or False + + # avoid overwriting existing (presumably correct) values with geolocation data + if partner.country_id or partner.zip or partner.city: + values.pop('city', None) + values.pop('country_id', None) + if partner.lang: + values.pop('lang', None) + if partner_user: # user exists, modify it according to values values.pop('login', None) diff --git a/addons/document/document.py b/addons/document/document.py index 9008f32cd20..47c30de7ca6 100644 --- a/addons/document/document.py +++ b/addons/document/document.py @@ -235,18 +235,8 @@ class document_directory(osv.osv): _parent(dir_id, path) return path - def _check_recursion(self, cr, uid, ids, context=None): - level = 100 - while len(ids): - cr.execute('select distinct parent_id from document_directory where id in ('+','.join(map(str,ids))+')') - ids = filter(None, map(lambda x:x[0], cr.fetchall())) - if not level: - return False - level -= 1 - return True - _constraints = [ - (_check_recursion, 'Error! You cannot create recursive directories.', ['parent_id']) + (osv.osv._check_recursion, 'Error! You cannot create recursive directories.', ['parent_id']) ] def onchange_content_id(self, cr, uid, ids, ressource_type_id): diff --git a/addons/hr_holidays/hr_holidays.py b/addons/hr_holidays/hr_holidays.py index ed25b75aa83..d38cb705339 100644 --- a/addons/hr_holidays/hr_holidays.py +++ b/addons/hr_holidays/hr_holidays.py @@ -522,8 +522,8 @@ class hr_employee(osv.osv): where h.state='validate' and s.limit=False and - h.employee_id in (%s) - group by h.employee_id"""% (','.join(map(str,ids)),) ) + h.employee_id in %s + group by h.employee_id""", (tuple(ids),)) res = cr.dictfetchall() remaining = {} for r in res: diff --git a/addons/l10n_multilang/__openerp__.py b/addons/l10n_multilang/__openerp__.py index 5c91e1472c4..0b804c27263 100644 --- a/addons/l10n_multilang/__openerp__.py +++ b/addons/l10n_multilang/__openerp__.py @@ -32,7 +32,7 @@ templates to target objects. """, 'website': 'http://www.openerp.com', - 'depends' : ['account_accountant'], + 'depends' : ['account'], 'data': [], 'demo': [], 'installable': True, diff --git a/addons/mail/static/src/css/mail.css b/addons/mail/static/src/css/mail.css index 0abd8ab1b4e..0faf7fc5e0d 100644 --- a/addons/mail/static/src/css/mail.css +++ b/addons/mail/static/src/css/mail.css @@ -164,15 +164,9 @@ border-radius: 3px; margin: 0px; padding-left: 3px; - padding-right: 15px; + padding-right: 5px; margin-right: 5px; } -.openerp .oe_mail .oe_mail_vote_count .oe_e{ - position: absolute; - bottom: 1px; - right: 2px; - font-size: 26px; -} /* c) Message action icons */ diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml index 3aea4a19555..116665724a9 100644 --- a/addons/mail/static/src/xml/mail.xml +++ b/addons/mail/static/src/xml/mail.xml @@ -344,7 +344,7 @@ - 8 + like diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py index d1c3e7facd4..a1077a3fffa 100644 --- a/addons/mass_mailing/models/mass_mailing.py +++ b/addons/mass_mailing/models/mass_mailing.py @@ -113,26 +113,38 @@ class MassMailingCampaign(osv.Model): def _get_statistics(self, cr, uid, ids, name, arg, context=None): """ Compute statistics of the mass mailing campaign """ - Statistics = self.pool['mail.mail.statistics'] - results = dict.fromkeys(ids, False) - for cid in ids: - stat_ids = Statistics.search(cr, uid, [('mass_mailing_campaign_id', '=', cid)], context=context) - stats = Statistics.browse(cr, uid, stat_ids, context=context) - results[cid] = { - 'total': len(stats), - 'failed': len([s for s in stats if not s.scheduled is False and s.sent is False and not s.exception is False]), - 'scheduled': len([s for s in stats if not s.scheduled is False and s.sent is False and s.exception is False]), - 'sent': len([s for s in stats if not s.sent is False]), - 'opened': len([s for s in stats if not s.opened is False]), - 'replied': len([s for s in stats if not s.replied is False]), - 'bounced': len([s for s in stats if not s.bounced is False]), - } - results[cid]['delivered'] = results[cid]['sent'] - results[cid]['bounced'] - results[cid]['received_ratio'] = 100.0 * results[cid]['delivered'] / (results[cid]['total'] or 1) - results[cid]['opened_ratio'] = 100.0 * results[cid]['opened'] / (results[cid]['total'] or 1) - results[cid]['replied_ratio'] = 100.0 * results[cid]['replied'] / (results[cid]['total'] or 1) + results = {} + cr.execute(""" + SELECT + c.id as campaign_id, + COUNT(s.id) AS total, + COUNT(CASE WHEN s.sent is not null THEN 1 ELSE null END) AS sent, + COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is null THEN 1 ELSE null END) AS scheduled, + COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is not null THEN 1 ELSE null END) AS failed, + COUNT(CASE WHEN s.id is not null AND s.bounced is null THEN 1 ELSE null END) AS delivered, + COUNT(CASE WHEN s.opened is not null THEN 1 ELSE null END) AS opened, + COUNT(CASE WHEN s.replied is not null THEN 1 ELSE null END) AS replied , + COUNT(CASE WHEN s.bounced is not null THEN 1 ELSE null END) AS bounced + FROM + mail_mail_statistics s + RIGHT JOIN + mail_mass_mailing_campaign c + ON (c.id = s.mass_mailing_campaign_id) + WHERE + c.id IN %s + GROUP BY + c.id + """, (tuple(ids), )) + for row in cr.dictfetchall(): + results[row.pop('campaign_id')] = row + total = row['total'] or 1 + row['delivered'] = row['sent'] - row['bounced'] + row['received_ratio'] = 100.0 * row['delivered'] / total + row['opened_ratio'] = 100.0 * row['opened'] / total + row['replied_ratio'] = 100.0 * row['replied'] / total return results + _columns = { 'name': fields.char('Name', required=True), 'stage_id': fields.many2one('mail.mass_mailing.stage', 'Stage', required=True), @@ -283,26 +295,38 @@ class MassMailing(osv.Model): def _get_statistics(self, cr, uid, ids, name, arg, context=None): """ Compute statistics of the mass mailing campaign """ - Statistics = self.pool['mail.mail.statistics'] - results = dict.fromkeys(ids, False) - for mid in ids: - stat_ids = Statistics.search(cr, uid, [('mass_mailing_id', '=', mid)], context=context) - stats = Statistics.browse(cr, uid, stat_ids, context=context) - results[mid] = { - 'total': len(stats), - 'failed': len([s for s in stats if not s.scheduled is False and s.sent is False and not s.exception is False]), - 'scheduled': len([s for s in stats if not s.scheduled is False and s.sent is False and s.exception is False]), - 'sent': len([s for s in stats if not s.sent is False]), - 'opened': len([s for s in stats if not s.opened is False]), - 'replied': len([s for s in stats if not s.replied is False]), - 'bounced': len([s for s in stats if not s.bounced is False]), - } - results[mid]['delivered'] = results[mid]['sent'] - results[mid]['bounced'] - results[mid]['received_ratio'] = 100.0 * results[mid]['delivered'] / (results[mid]['total'] or 1) - results[mid]['opened_ratio'] = 100.0 * results[mid]['opened'] / (results[mid]['total'] or 1) - results[mid]['replied_ratio'] = 100.0 * results[mid]['replied'] / (results[mid]['total'] or 1) + results = {} + cr.execute(""" + SELECT + m.id as mailing_id, + COUNT(s.id) AS total, + COUNT(CASE WHEN s.sent is not null THEN 1 ELSE null END) AS sent, + COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is null THEN 1 ELSE null END) AS scheduled, + COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is not null THEN 1 ELSE null END) AS failed, + COUNT(CASE WHEN s.id is not null AND s.bounced is null THEN 1 ELSE null END) AS delivered, + COUNT(CASE WHEN s.opened is not null THEN 1 ELSE null END) AS opened, + COUNT(CASE WHEN s.replied is not null THEN 1 ELSE null END) AS replied , + COUNT(CASE WHEN s.bounced is not null THEN 1 ELSE null END) AS bounced + FROM + mail_mail_statistics s + RIGHT JOIN + mail_mass_mailing m + ON (m.id = s.mass_mailing_id) + WHERE + m.id IN %s + GROUP BY + m.id + """, (tuple(ids), )) + for row in cr.dictfetchall(): + results[row.pop('mailing_id')] = row + total = row['total'] or 1 + row['delivered'] = row['sent'] - row['bounced'] + row['received_ratio'] = 100.0 * row['delivered'] / total + row['opened_ratio'] = 100.0 * row['opened'] / total + row['replied_ratio'] = 100.0 * row['replied'] / total return results + def _get_mailing_model(self, cr, uid, context=None): res = [] for model_name in self.pool: diff --git a/addons/project_issue_sheet/project_issue_sheet_view.xml b/addons/project_issue_sheet/project_issue_sheet_view.xml index 6d813b44239..39ff9ca0010 100644 --- a/addons/project_issue_sheet/project_issue_sheet_view.xml +++ b/addons/project_issue_sheet/project_issue_sheet_view.xml @@ -32,6 +32,7 @@ + diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 0aaa1362290..25d67dd1df1 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -1922,7 +1922,6 @@ class stock_move(osv.osv): result = { 'product_uom_qty': 0.00 } - warning = {} if (not product_id) or (product_uos_qty <= 0.0): result['product_uos_qty'] = 0.0 @@ -1931,21 +1930,14 @@ class stock_move(osv.osv): product_obj = self.pool.get('product.product') uos_coeff = product_obj.read(cr, uid, product_id, ['uos_coeff']) - # Warn if the quantity was decreased - for move in self.read(cr, uid, ids, ['product_uos_qty']): - if product_uos_qty < move['product_uos_qty']: - warning.update({ - 'title': _('Warning: No Back Order'), - 'message': _("By changing the quantity here, you accept the " - "new quantity as complete: OpenERP will not " - "automatically generate a Back Order.")}) - break + # No warning if the quantity was decreased to avoid double warnings: + # The clients should call onchange_quantity too anyway if product_uos and product_uom and (product_uom != product_uos): result['product_uom_qty'] = product_uos_qty / uos_coeff['uos_coeff'] else: result['product_uom_qty'] = product_uos_qty - return {'value': result, 'warning': warning} + return {'value': result} def onchange_product_id(self, cr, uid, ids, prod_id=False, loc_id=False, loc_dest_id=False, partner_id=False): """ On change of product id, if finds UoM, UoS, quantity and UoS quantity. diff --git a/addons/survey/survey.py b/addons/survey/survey.py index 590d7ae22c8..1389e63e9e2 100644 --- a/addons/survey/survey.py +++ b/addons/survey/survey.py @@ -360,7 +360,7 @@ class survey_survey(osv.Model): for cell in product(rows.keys(), answers.keys()): res[cell] = 0 for input_line in question.user_input_line_ids: - if input_line.answer_type == 'suggestion' and not(current_filters) or input_line.user_input_id.id in current_filters: + if input_line.answer_type == 'suggestion' and (not(current_filters) or input_line.user_input_id.id in current_filters): res[(input_line.value_suggested_row.id, input_line.value_suggested.id)] += 1 result_summary = {'answers': answers, 'rows': rows, 'result': res} @@ -993,6 +993,8 @@ class survey_user_input_line(osv.Model): def __get_mark(self, cr, uid, value_suggested, context=None): try: mark = self.pool.get('survey.label').browse(cr, uid, int(value_suggested), context=context).quizz_mark + except AttributeError: + mark = 0.0 except KeyError: mark = 0.0 except ValueError: @@ -1140,7 +1142,7 @@ class survey_user_input_line(osv.Model): comment_answer = post.pop(("%s_%s" % (answer_tag, 'comment')), '').strip() if comment_answer: - vals.update({'answer_type': 'text', 'value_text': comment_answer}) + vals.update({'answer_type': 'text', 'value_text': comment_answer, 'skipped': False}) self.create(cr, uid, vals, context=context) return True diff --git a/addons/web_linkedin/web_linkedin.py b/addons/web_linkedin/web_linkedin.py index 83132511a17..a0919f7968e 100644 --- a/addons/web_linkedin/web_linkedin.py +++ b/addons/web_linkedin/web_linkedin.py @@ -34,7 +34,7 @@ class Binary(openerp.http.Controller): _scheme, _netloc, path, params, query, fragment = urlparse(url) # media.linkedin.com is the master domain for LinkedIn media (replicated to CDNs), # so forcing it should always work and prevents abusing this method to load arbitrary URLs - url = urlunparse(('http', 'media.linkedin.com', path, params, query, fragment)) + url = urlunparse(('http', 'media.licdn.com', path, params, query, fragment)) bfile = urllib2.urlopen(url) return base64.b64encode(bfile.read()) diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py index f3bb906d4ba..2153e293240 100644 --- a/addons/website/models/ir_http.py +++ b/addons/website/models/ir_http.py @@ -10,7 +10,7 @@ import werkzeug.routing import openerp from openerp.addons.base import ir from openerp.addons.base.ir import ir_qweb -from openerp.addons.website.models.website import slug, url_for +from openerp.addons.website.models.website import slug, url_for, _UNSLUG_RE from openerp.http import request from openerp.osv import orm @@ -209,7 +209,7 @@ class ModelConverter(ir.ir_http.ModelConverter): def __init__(self, url_map, model=False, domain='[]'): super(ModelConverter, self).__init__(url_map, model) self.domain = domain - self.regex = r'(?:[A-Za-z0-9-_]+?-)?(\d+)(?=$|/)' + self.regex = _UNSLUG_RE.pattern def to_url(self, value): return slug(value) @@ -218,7 +218,7 @@ class ModelConverter(ir.ir_http.ModelConverter): m = re.match(self.regex, value) _uid = RequestUID(value=value, match=m, converter=self) return request.registry[self.model].browse( - request.cr, _uid, int(m.group(1)), context=request.context) + request.cr, _uid, int(m.group(2)), context=request.context) def generate(self, cr, uid, query=None, args=None, context=None): obj = request.registry[self.model] diff --git a/addons/website/models/website.py b/addons/website/models/website.py index 4cda81c7448..5921e6cfca4 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -102,11 +102,23 @@ def slug(value): else: # assume name_search result tuple id, name = value - slugname = slugify(name or '') + slugname = slugify(name or '').strip().strip('-') if not slugname: return str(id) return "%s-%d" % (slugname, id) + +_UNSLUG_RE = re.compile(r'(?:(\w{1,2}|\w[a-zA-Z0-9-_]+?\w)-)?(-?\d+)(?=$|/)') + +def unslug(s): + """Extract slug and id from a string. + Always return un 2-tuple (str|None, int|None) + """ + m = _UNSLUG_RE.match(s) + if not m: + return None, None + return m.group(1), int(m.group(2)) + def urlplus(url, params): return werkzeug.Href(url)(params or None) @@ -237,7 +249,7 @@ class website(osv.osv): # Compute Pager page_count = int(math.ceil(float(total) / step)) - page = max(1, min(int(page), page_count)) + page = max(1, min(int(page if str(page).isdigit() else 1), page_count)) scope -= 1 pmin = max(page - int(math.floor(scope/2)), 1) diff --git a/addons/website/tests/test_converter.py b/addons/website/tests/test_converter.py index ca72a361ac2..6a6b046ca93 100644 --- a/addons/website/tests/test_converter.py +++ b/addons/website/tests/test_converter.py @@ -9,10 +9,32 @@ from lxml.builder import E from openerp.tests import common from openerp.addons.base.ir import ir_qweb from openerp.addons.website.models.ir_qweb import html_to_text +from openerp.addons.website.models.website import unslug impl = getDOMImplementation() document = impl.createDocument(None, None, None) +class TestUnslug(unittest2.TestCase): + def test_unslug(self): + tests = { + '': (None, None), + 'foo': (None, None), + 'foo-': (None, None), + '-': (None, None), + 'foo-1': ('foo', 1), + 'foo-bar-1': ('foo-bar', 1), + 'foo--1': ('foo', -1), + '1': (None, 1), + '1-1': ('1', 1), + '--1': (None, None), + 'foo---1': (None, None), + 'foo1': (None, None), + } + + for slug, expected in tests.iteritems(): + self.assertEqual(unslug(slug), expected) + + class TestHTMLToText(unittest2.TestCase): def test_rawstring(self): self.assertEqual( diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index c23e18ebf8e..d0c4231eac6 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -426,7 +426,7 @@ - +
diff --git a/addons/website_crm/controllers/main.py b/addons/website_crm/controllers/main.py index 5f6853b391d..37d661e8a04 100644 --- a/addons/website_crm/controllers/main.py +++ b/addons/website_crm/controllers/main.py @@ -52,6 +52,8 @@ class contactus(http.Controller): elif field_name not in _TECHNICAL: # allow to add some free fields or blacklisted field like ID post_description.append("%s: %s" % (field_name, field_value)) + if "name" not in kwargs and values.get("contact_name"): # if kwarg.name is empty, it's an error, we cannot copy the contact_name + values["name"] = values.get("contact_name") # fields validation : Check that required field from model crm_lead exists error = set(field for field in _REQUIRED if not kwargs.get(field)) diff --git a/addons/website_crm_partner_assign/controllers/main.py b/addons/website_crm_partner_assign/controllers/main.py index c23bddc5ea2..ea0ec1506ba 100644 --- a/addons/website_crm_partner_assign/controllers/main.py +++ b/addons/website_crm_partner_assign/controllers/main.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- import logging -import re import werkzeug _logger = logging.getLogger(__name__) @@ -13,7 +12,7 @@ except ImportError: from openerp import SUPERUSER_ID from openerp.addons.web import http from openerp.addons.web.http import request -from openerp.addons.website.models.website import slug +from openerp.addons.website.models.website import slug, unslug from openerp.tools.translate import _ @@ -146,7 +145,7 @@ class WebsiteCrmPartnerAssign(http.Controller): # Do not use semantic controller due to SUPERUSER_ID @http.route(['/partners/'], type='http', auth="public", website=True) def partners_detail(self, partner_id, partner_name='', **post): - mo = re.search('-([-0-9]+)$', str(partner_id)) + _, partner_id = unslug(partner_id) current_grade, current_country = None, None grade_id = post.get('grade_id') country_id = post.get('country_id') @@ -158,8 +157,7 @@ class WebsiteCrmPartnerAssign(http.Controller): country_ids = request.registry['res.country'].exists(request.cr, request.uid, int(country_id), context=request.context) if country_ids: current_country = request.registry['res.country'].browse(request.cr, request.uid, country_ids[0], context=request.context) - if mo: - partner_id = int(mo.group(1)) + if partner_id: partner = request.registry['res.partner'].browse(request.cr, SUPERUSER_ID, partner_id, context=request.context) if partner.exists() and partner.website_published: values = { diff --git a/addons/website_customer/controllers/main.py b/addons/website_customer/controllers/main.py index 3e396f23fcf..fcb2d0d8ded 100644 --- a/addons/website_customer/controllers/main.py +++ b/addons/website_customer/controllers/main.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- -import re - import openerp from openerp import SUPERUSER_ID from openerp.addons.web import http +from openerp.addons.website.models.website import unslug from openerp.tools.translate import _ from openerp.addons.web.http import request import werkzeug.urls @@ -84,9 +83,8 @@ class WebsiteCustomer(http.Controller): # Do not use semantic controller due to SUPERUSER_ID @http.route(['/customers/'], type='http', auth="public", website=True) def partners_detail(self, partner_id, **post): - mo = re.search('-([-0-9]+)$', str(partner_id)) - if mo: - partner_id = int(mo.group(1)) + _, partner_id = unslug(partner_id) + if partner_id: partner = request.registry['res.partner'].browse(request.cr, SUPERUSER_ID, partner_id, context=request.context) if partner.exists() and partner.website_published: values = {} diff --git a/addons/website_event/controllers/main.py b/addons/website_event/controllers/main.py index 16a6bd24edf..71432404f3a 100644 --- a/addons/website_event/controllers/main.py +++ b/addons/website_event/controllers/main.py @@ -25,6 +25,7 @@ from datetime import datetime, timedelta from dateutil.relativedelta import relativedelta import werkzeug.urls +from werkzeug.exceptions import NotFound from openerp import SUPERUSER_ID from openerp import http @@ -175,6 +176,16 @@ class website_event(http.Controller): 'event': event, 'main_object': event } + + if '.' not in page: + page = 'website_event.%s' % page + + try: + request.website.get_template(page) + except ValueError, e: + # page not found + raise NotFound + return request.website.render(page, values) @http.route(['/event/'], type='http', auth="public", website=True) diff --git a/addons/website_membership/controllers/main.py b/addons/website_membership/controllers/main.py index f4eb4bc46b6..73c894ac46f 100644 --- a/addons/website_membership/controllers/main.py +++ b/addons/website_membership/controllers/main.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- -import re - from openerp import SUPERUSER_ID from openerp.addons.web import http from openerp.addons.web.http import request +from openerp.addons.website.models.website import unslug from openerp.tools.translate import _ import werkzeug.urls @@ -105,9 +104,8 @@ class WebsiteMembership(http.Controller): # Do not use semantic controller due to SUPERUSER_ID @http.route(['/members/'], type='http', auth="public", website=True) def partners_detail(self, partner_id, **post): - mo = re.search('-([-0-9]+)$', str(partner_id)) - if mo: - partner_id = int(mo.group(1)) + _, partner_id = unslug(partner_id) + if partner_id: partner = request.registry['res.partner'].browse(request.cr, SUPERUSER_ID, partner_id, context=request.context) if partner.exists() and partner.website_published: values = {} diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index c7af837aff1..7d6b92ad315 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -592,7 +592,7 @@ class res_partner(osv.osv, format_address): """ Supported syntax: - 'Raoul ': will find name and email address - otherwise: default, everything is set as the name """ - emails = tools.email_split(text) + emails = tools.email_split(text.replace(' ',',')) if emails: email = emails[0] name = text[:text.index(email)].replace('"', '').replace('<', '').strip() diff --git a/openerp/addons/base/res/res_partner_view.xml b/openerp/addons/base/res/res_partner_view.xml index 50045aaf270..e658ddc0fe7 100644 --- a/openerp/addons/base/res/res_partner_view.xml +++ b/openerp/addons/base/res/res_partner_view.xml @@ -159,8 +159,8 @@
-