[MERGE] Forward-port saas-5 up to d9cda97

This commit is contained in:
Olivier Dony 2014-07-05 01:45:45 +02:00
commit 34bfa1b44f
26 changed files with 134 additions and 99 deletions

View File

@ -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:

View File

@ -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)

View File

@ -1,6 +1,9 @@
# -*- coding: utf-8 -*-
import calendar
from datetime import date
from dateutil import relativedelta
import json
from openerp import tools
from openerp.osv import fields, osv
@ -30,8 +33,8 @@ class crm_case_section(osv.Model):
res[id] = dict()
lead_domain = lead_pre_domain + [('section_id', '=', id)]
opp_domain = opp_pre_domain + [('section_id', '=', id)]
res[id]['monthly_open_leads'] = self.__get_bar_values(cr, uid, obj, lead_domain, ['create_date'], 'create_date_count', 'create_date', context=context)
res[id]['monthly_planned_revenue'] = self.__get_bar_values(cr, uid, obj, opp_domain, ['planned_revenue', 'date_deadline'], 'planned_revenue', 'date_deadline', context=context)
res[id]['monthly_open_leads'] = json.dumps(self.__get_bar_values(cr, uid, obj, lead_domain, ['create_date'], 'create_date_count', 'create_date', context=context))
res[id]['monthly_planned_revenue'] = json.dumps(self.__get_bar_values(cr, uid, obj, opp_domain, ['planned_revenue', 'date_deadline'], 'planned_revenue', 'date_deadline', context=context))
return res
_columns = {

View File

@ -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):

View File

@ -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:

View File

@ -32,7 +32,7 @@
templates to target objects.
""",
'website': 'http://www.openerp.com',
'depends' : ['account_accountant'],
'depends' : ['account'],
'data': [],
'demo': [],
'installable': True,

View File

@ -132,15 +132,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 */

View File

@ -341,7 +341,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>

View File

@ -62,8 +62,7 @@
<field name="search_view_id" ref="view_campaign_analysis_search"/>
</record>
<menuitem name="Marketing" id="base.menu_report_marketing" parent="base.menu_reporting" sequence="45"/>
<menuitem action="action_campaign_analysis_all" id="menu_action_campaign_analysis_all" parent="base.menu_report_marketing" sequence="2"/>
<menuitem action="action_campaign_analysis_all" id="menu_action_campaign_analysis_all" parent="base.marketing_reporting_menu" sequence="20"/>
</data>
</openerp>

View File

@ -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:

View File

@ -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>

View File

@ -3,6 +3,7 @@
import calendar
from datetime import date
from dateutil import relativedelta
import json
from openerp import tools
from openerp.osv import fields, osv
@ -20,9 +21,9 @@ class crm_case_section(osv.osv):
for id in ids:
res[id] = dict()
created_domain = [('section_id', '=', id), ('state', '=', 'draft'), ('date_order', '>=', date_begin), ('date_order', '<=', date_end)]
res[id]['monthly_quoted'] = self.__get_bar_values(cr, uid, obj, created_domain, ['amount_total', 'date_order'], 'amount_total', 'date_order', context=context)
res[id]['monthly_quoted'] = json.dumps(self.__get_bar_values(cr, uid, obj, created_domain, ['amount_total', 'date_order'], 'amount_total', 'date_order', context=context))
validated_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'sent', 'cancel']), ('date_order', '>=', date_begin), ('date_order', '<=', date_end)]
res[id]['monthly_confirmed'] = self.__get_bar_values(cr, uid, obj, validated_domain, ['amount_total', 'date_order'], 'amount_total', 'date_order', context=context)
res[id]['monthly_confirmed'] = json.dumps(self.__get_bar_values(cr, uid, obj, validated_domain, ['amount_total', 'date_order'], 'amount_total', 'date_order', context=context))
return res
def _get_invoices_data(self, cr, uid, ids, field_name, arg, context=None):
@ -33,11 +34,11 @@ class crm_case_section(osv.osv):
date_end = month_begin.replace(day=calendar.monthrange(month_begin.year, month_begin.month)[1]).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
for id in ids:
created_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'cancel']), ('date', '>=', date_begin), ('date', '<=', date_end)]
res[id] = self.__get_bar_values(cr, uid, obj, created_domain, ['price_total', 'date'], 'price_total', 'date', context=context)
res[id] = json.dumps(self.__get_bar_values(cr, uid, obj, created_domain, ['price_total', 'date'], 'price_total', 'date', context=context))
return res
_columns = {
'use_quotations': fields.boolean('Opportunities', help="Check this box to manage quotations in this sales team."),
'use_quotations': fields.boolean('Quotations', help="Check this box to manage quotations in this sales team."),
'invoiced_forecast': fields.integer(string='Invoice Forecast',
help="Forecast of the invoice revenue for the current month. This is the amount the sales \n"
"team should invoice this month. It is used to compute the progression ratio \n"

View File

@ -21,7 +21,7 @@
from datetime import date, datetime
from dateutil import relativedelta
import json
import time
from openerp.osv import fields, osv
@ -1930,7 +1930,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
@ -1939,21 +1938,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.
@ -4102,12 +4094,12 @@ class stock_picking_type(osv.osv):
tristates = []
for picking in picking_obj.browse(cr, uid, picking_ids, context=context):
if picking.date_done > picking.date:
tristates.insert(0, {'tooltip': picking.name or '' + _(': Late'), 'value': -1})
tristates.insert(0, {'tooltip': picking.name or '' + ": " + _('Late'), 'value': -1})
elif picking.backorder_id:
tristates.insert(0, {'tooltip': picking.name or '' + _(': Backorder exists'), 'value': 0})
tristates.insert(0, {'tooltip': picking.name or '' + ": " + _('Backorder exists'), 'value': 0})
else:
tristates.insert(0, {'tooltip': picking.name or '' + _(': OK'), 'value': 1})
res[picking_type_id] = tristates
tristates.insert(0, {'tooltip': picking.name or '' + ": " + _('OK'), 'value': 1})
res[picking_type_id] = json.dumps(tristates)
return res
def _get_picking_count(self, cr, uid, ids, field_names, arg, context=None):

View File

@ -365,7 +365,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}
@ -998,6 +998,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:
@ -1145,7 +1147,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

View File

@ -38,6 +38,9 @@ instance.web_kanban.GaugeWidget = instance.web_kanban.AbstractField.extend({
var title = this.$node.html() || this.field.string;
// current gauge value
var val = this.field.value;
if (_.isArray(JSON.parse(val))) {
val = JSON.parse(val);
}
var value = _.isArray(val) && val.length ? val[val.length-1]['value'] : val;
// displayed value under gauge
var gauge_value = value;

View File

@ -11,8 +11,9 @@ instance.web_kanban.SparklineBarWidget = instance.web_kanban.AbstractField.exten
var self = this;
var title = this.$node.html() || this.field.string;
setTimeout(function () {
var value = _.pluck(self.field.value, 'value');
var tooltips = _.pluck(self.field.value, 'tooltip');
var field_value = JSON.parse(self.field.value);
var value = _.pluck(field_value, 'value');
var tooltips = _.pluck(field_value, 'tooltip');
var suffix = self.options.tooltip_suffix || "";
var tooltipFormat = self.options.type == 'tristate' && '{{offset:offset}}' + suffix || '{{offset:offset}} {{value:value}}' + suffix
var sparkline_options = _.extend({

View File

@ -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())

View File

@ -269,7 +269,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)

View File

@ -393,7 +393,7 @@
<table class="table js_kanban">
<thead>
<tr>
<t t-set="width" t-valuef="{{ round(100.0 / len(objects), 2) }}%"/>
<t t-set="width" t-valuef="{{ round(100.0 / (len(objects) if objects else 1), 2) }}%"/>
<t t-foreach="objects" t-as="obj">
<th t-att-width="width">
<div t-field="obj['column_id'].name" class="text-center"></div>

View File

@ -2,7 +2,6 @@
import base64
from openerp.tools.translate import _
from openerp.addons.web import http
from openerp.addons.web.http import request
from openerp import SUPERUSER_ID
@ -53,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))

View File

@ -24,6 +24,7 @@ from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
import werkzeug.urls
from werkzeug.exceptions import NotFound
from openerp import http
from openerp import tools
@ -165,6 +166,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/<model("event.event"):event>'], type='http', auth="public", website=True)

View File

@ -16,5 +16,5 @@ Delivery Costs
],
'demo': [],
'qweb': [],
'installable': False,
'installable': True,
}

View File

@ -10,7 +10,7 @@ class website_sale(openerp.addons.website_sale.controllers.main.website_sale):
@http.route(['/shop/payment'], type='http', auth="public", website=True)
def payment(self, **post):
cr, uid, context = request.cr, request.uid, request.context
order = self.get_order()
order = request.website.sale_get_order(context=context)
carrier_id = post.get('carrier_id')
if carrier_id:
carrier_id = int(carrier_id)

View File

@ -592,7 +592,7 @@ class res_partner(osv.osv, format_address):
""" Supported syntax:
- 'Raoul <raoul@grosbedon.fr>': 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()

View File

@ -158,8 +158,8 @@
<div class="oe_edit_only">
<field name="use_parent_address" class="oe_inline"
on_change="onchange_address(use_parent_address, parent_id)"
attrs="{'invisible': [('parent_id','=', False),('use_parent_address','=',False)]}"/>
<label for="use_parent_address" attrs="{'invisible': [('parent_id','=', False),('use_parent_address','=',False)]}"/>
attrs="{'invisible': ['|', ('is_company', '=', True),('parent_id', '=', False)]}"/>
<label for="use_parent_address" attrs="{'invisible': ['|', ('is_company', '=', True), ('parent_id', '=', False)]}"/>
</div>
<button name="open_parent" type="object" string="(edit company address)" class="oe_link oe_edit_only"
attrs="{'invisible': ['|',('parent_id','=', False),('use_parent_address','=',False)]}"/>

View File

@ -379,7 +379,11 @@ class _rml_canvas(object):
v = utils.attr_get(node, ['x','y'])
text=self._textual(node, **v)
text = utils.xml2str(text)
self.canvas.drawString(text=text, **v)
try:
self.canvas.drawString(text=text, **v)
except TypeError:
_logger.error("Bad RML: <drawString> tag requires attributes 'x' and 'y'!")
raise
def _drawCenteredString(self, node):
v = utils.attr_get(node, ['x','y'])
@ -1005,10 +1009,10 @@ class _rml_template(object):
story_cnt = 0
for node_story in node_stories:
if story_cnt > 0:
# Reset Page Number with new story tag
fis.append(PageReset())
fis.append(platypus.PageBreak())
fis += r.render(node_story)
# Reset Page Number with new story tag
fis.append(PageReset())
story_cnt += 1
try:
if self.localcontext and self.localcontext.get('internal_header',False):