Skip to content

Commit

Permalink
[MERGE] Forward-port saas-4 up to 5ceded9
Browse files Browse the repository at this point in the history
  • Loading branch information
odony committed Jul 4, 2014
2 parents f0ef8ac + 5ceded9 commit d9cda97
Show file tree
Hide file tree
Showing 24 changed files with 157 additions and 100 deletions.
3 changes: 2 additions & 1 deletion addons/auth_oauth/controllers/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
8 changes: 8 additions & 0 deletions addons/auth_signup/res_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,14 @@ def signup(self, cr, uid, values, token=None, context=None):
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)
Expand Down
12 changes: 1 addition & 11 deletions addons/document/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,18 +235,8 @@ def _parent(dir_id, path):
_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):
Expand Down
4 changes: 2 additions & 2 deletions addons/hr_holidays/hr_holidays.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,8 +522,8 @@ def _get_remaining_days(self, cr, uid, ids, name, args, context=None):
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:
Expand Down
2 changes: 1 addition & 1 deletion addons/l10n_multilang/__openerp__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
templates to target objects.
""",
'website': 'http://www.openerp.com',
'depends' : ['account_accountant'],
'depends' : ['account'],
'data': [],
'demo': [],
'installable': True,
Expand Down
8 changes: 1 addition & 7 deletions addons/mail/static/src/css/mail.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

Expand Down
2 changes: 1 addition & 1 deletion addons/mail/static/src/xml/mail.xml
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@
<span t-name="mail.thread.message.vote">
<span class="oe_mail_vote_count" t-if='widget.vote_nb > 0'>
<t t-esc='widget.vote_nb' />
<span class='oe_e'>8</span>
<i class="fa fa-thumbs-o-up"></i>
</span>
<a href='#' class="oe_msg_vote">
<t t-if="!widget.has_voted">like</t>
Expand Down
96 changes: 60 additions & 36 deletions addons/mass_mailing/models/mass_mailing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -283,26 +295,38 @@ def _get_daily_statistics(self, cr, uid, ids, field_name, arg, context=None):

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:
Expand Down
1 change: 1 addition & 0 deletions addons/project_issue_sheet/project_issue_sheet_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<field invisible="1" name="product_uom_id" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)"/>
<field invisible="1" name="amount"/>
<field invisible="1" name="general_account_id"/>
<field invisible="1" name="to_invoice"/>
</tree>
</field>
</page>
Expand Down
14 changes: 3 additions & 11 deletions addons/stock/stock.py
Original file line number Diff line number Diff line change
Expand Up @@ -1922,7 +1922,6 @@ def onchange_uos_quantity(self, cr, uid, ids, product_id, product_uos_qty,
result = {
'product_uom_qty': 0.00
}
warning = {}

if (not product_id) or (product_uos_qty <= 0.0):
result['product_uos_qty'] = 0.0
Expand All @@ -1931,21 +1930,14 @@ def onchange_uos_quantity(self, cr, uid, ids, product_id, product_uos_qty,
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.
Expand Down
6 changes: 4 additions & 2 deletions addons/survey/survey.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ def prepare_result(self, cr, uid, question, current_filters=[], context=None):
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}

Expand Down Expand Up @@ -993,6 +993,8 @@ def _check_answer_type(self, cr, uid, ids, context=None):
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:
Expand Down Expand Up @@ -1140,7 +1142,7 @@ def save_line_simple_choice(self, cr, uid, user_input_id, question, post, answer

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
Expand Down
2 changes: 1 addition & 1 deletion addons/web_linkedin/web_linkedin.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def url2binary(self, url):
_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())

Expand Down
6 changes: 3 additions & 3 deletions addons/website/models/ir_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
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

Expand Down Expand Up @@ -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)
Expand All @@ -218,7 +218,7 @@ def to_python(self, value):
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]
Expand Down
16 changes: 14 additions & 2 deletions addons/website/models/website.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -237,7 +249,7 @@ def pager(self, cr, uid, ids, url, total, page=1, step=30, scope=5, url_args=Non
# 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)
Expand Down
22 changes: 22 additions & 0 deletions addons/website/tests/test_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,32 @@
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(
Expand Down
2 changes: 1 addition & 1 deletion addons/website/views/website_templates.xml
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@
<table class="table js_kanban">
<thead>
<tr>
<t t-set="width" t-value="str(round(100.0 / len(objects), 2)) + '%'"/>
<t t-set="width" t-value="str(round(100.0 / (len(objects) if objects else 1), 2)) + '%'"/>
<t t-foreach="objects">
<th t-att-width="width">
<div t-field="column_id.name" class="text-center"></div>
Expand Down
2 changes: 2 additions & 0 deletions addons/website_crm/controllers/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ def dict_to_str(title, dictvar):
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))

Expand Down
Loading

0 comments on commit d9cda97

Please sign in to comment.