[MERGE] forward port of branch saas-5 up to b1c0bc0
This commit is contained in:
commit
a5908c5812
|
@ -3,6 +3,7 @@ _build/
|
|||
|
||||
# dotfiles
|
||||
.*
|
||||
!.gitignore
|
||||
# compiled python files
|
||||
*.py[co]
|
||||
# setup.py egg_info
|
||||
|
@ -12,7 +13,8 @@ _build/
|
|||
# hg stuff
|
||||
*.orig
|
||||
status
|
||||
|
||||
# odoo filestore
|
||||
openerp/filestore
|
||||
# generated for windows installer?
|
||||
install/win32/*.bat
|
||||
install/win32/meta.py
|
||||
|
|
|
@ -11,6 +11,7 @@ from openerp import SUPERUSER_ID
|
|||
from openerp import http
|
||||
from openerp.http import request
|
||||
from openerp.addons.web.controllers.main import db_monodb, ensure_db, set_cookie_and_redirect, login_and_redirect
|
||||
from openerp.addons.auth_signup.controllers.main import AuthSignupHome as Home
|
||||
from openerp.modules.registry import RegistryManager
|
||||
from openerp.tools.translate import _
|
||||
|
||||
|
@ -44,7 +45,7 @@ def fragment_to_query_string(func):
|
|||
#----------------------------------------------------------
|
||||
# Controller
|
||||
#----------------------------------------------------------
|
||||
class OAuthLogin(openerp.addons.web.controllers.main.Home):
|
||||
class OAuthLogin(Home):
|
||||
def list_providers(self):
|
||||
try:
|
||||
provider_obj = request.registry.get('auth.oauth.provider')
|
||||
|
@ -67,10 +68,13 @@ class OAuthLogin(openerp.addons.web.controllers.main.Home):
|
|||
return providers
|
||||
|
||||
def get_state(self, provider):
|
||||
redirect = request.params.get('redirect', 'web')
|
||||
if not redirect.startswith(('//', 'http://', 'https://')):
|
||||
redirect = '%s%s' % (request.httprequest.url_root, redirect)
|
||||
state = dict(
|
||||
d=request.session.db,
|
||||
p=provider['id'],
|
||||
r=request.httprequest.full_path
|
||||
r=redirect,
|
||||
)
|
||||
token = request.params.get('token')
|
||||
if token:
|
||||
|
@ -141,8 +145,7 @@ class OAuthController(http.Controller):
|
|||
menu = state.get('m')
|
||||
redirect = state.get('r')
|
||||
url = '/web'
|
||||
if redirect and not redirect.startswith('/auth_oauth/signin') and \
|
||||
(not redirect.startswith('/web/login') or 'redirect' in urlparse.urlsplit(redirect).query):
|
||||
if redirect:
|
||||
url = redirect
|
||||
elif action:
|
||||
url = '/web#action=%s' % action
|
||||
|
|
|
@ -6,6 +6,7 @@ import urllib2
|
|||
import simplejson
|
||||
|
||||
import openerp
|
||||
from openerp.addons.auth_signup.res_users import SignupError
|
||||
from openerp.osv import osv, fields
|
||||
from openerp import SUPERUSER_ID
|
||||
|
||||
|
@ -55,14 +56,37 @@ class res_users(osv.Model):
|
|||
|
||||
This method can be overridden to add alternative signin methods.
|
||||
"""
|
||||
oauth_uid = validation['user_id']
|
||||
user_ids = self.search(cr, uid, [("oauth_uid", "=", oauth_uid), ('oauth_provider_id', '=', provider)])
|
||||
if not user_ids:
|
||||
raise openerp.exceptions.AccessDenied()
|
||||
assert len(user_ids) == 1
|
||||
user = self.browse(cr, uid, user_ids[0], context=context)
|
||||
user.write({'oauth_access_token': params['access_token']})
|
||||
return user.login
|
||||
try:
|
||||
oauth_uid = validation['user_id']
|
||||
user_ids = self.search(cr, uid, [("oauth_uid", "=", oauth_uid), ('oauth_provider_id', '=', provider)])
|
||||
if not user_ids:
|
||||
raise openerp.exceptions.AccessDenied()
|
||||
assert len(user_ids) == 1
|
||||
user = self.browse(cr, uid, user_ids[0], context=context)
|
||||
user.write({'oauth_access_token': params['access_token']})
|
||||
return user.login
|
||||
except openerp.exceptions.AccessDenied, access_denied_exception:
|
||||
if context and context.get('no_user_creation'):
|
||||
return None
|
||||
state = simplejson.loads(params['state'])
|
||||
token = state.get('t')
|
||||
oauth_uid = validation['user_id']
|
||||
email = validation.get('email', 'provider_%s_user_%s' % (provider, oauth_uid))
|
||||
name = validation.get('name', email)
|
||||
values = {
|
||||
'name': name,
|
||||
'login': email,
|
||||
'email': email,
|
||||
'oauth_provider_id': provider,
|
||||
'oauth_uid': oauth_uid,
|
||||
'oauth_access_token': params['access_token'],
|
||||
'active': True,
|
||||
}
|
||||
try:
|
||||
_, login, _ = self.signup(cr, uid, values, token, context=context)
|
||||
return login
|
||||
except SignupError:
|
||||
raise access_denied_exception
|
||||
|
||||
def auth_oauth(self, cr, uid, provider, params, context=None):
|
||||
# Advice by Google (to avoid Confused Deputy Problem)
|
||||
|
|
|
@ -1273,7 +1273,7 @@ class calendar_event(osv.Model):
|
|||
if data.get('count'):
|
||||
data['end_type'] = 'count'
|
||||
else:
|
||||
data['end_type'] = 'final_date'
|
||||
data['end_type'] = 'end_date'
|
||||
return data
|
||||
|
||||
def message_get_subscription_data(self, cr, uid, ids, user_pid=None, context=None):
|
||||
|
|
|
@ -126,10 +126,10 @@
|
|||
<field name="use_leads"/><label for="use_leads" string="Leads"/>
|
||||
<field name="use_opportunities" class="oe_inline"/><label for="use_opportunities"/>
|
||||
</xpath>
|
||||
<xpath expr="//group/field[@name='active']" position="inside">
|
||||
<xpath expr="//group[@name='left']" position="after">
|
||||
<group>
|
||||
<label for="alias_name" string="Email Alias"
|
||||
attrs="p'invisible': [('alias_domain', '=', False)]}"/>
|
||||
attrs="{'invisible': [('alias_domain', '=', False)]}"/>
|
||||
<div name="alias_def"
|
||||
attrs="{'invisible': [('alias_domain', '=', False)]}">
|
||||
<field name="alias_id" class="oe_read_only oe_inline"
|
||||
|
|
|
@ -74,7 +74,7 @@ class sale_order(osv.Model):
|
|||
if not grid_id:
|
||||
raise osv.except_osv(_('No Grid Available!'), _('No grid matching for this carrier!'))
|
||||
|
||||
if order.state != 'draft':
|
||||
if order.state not in ('draft', 'sent'):
|
||||
raise osv.except_osv(_('Order not in Draft State!'), _('The order state have to be draft to add delivery lines.'))
|
||||
|
||||
grid = grid_obj.browse(cr, uid, grid_id, context=context)
|
||||
|
|
|
@ -39,16 +39,15 @@ instance.edi.EdiImport = instance.web.Widget.extend({
|
|||
});
|
||||
}
|
||||
else {
|
||||
$('<div>').dialog({
|
||||
modal: true,
|
||||
title: 'Import Successful!',
|
||||
buttons: {
|
||||
Ok: function() {
|
||||
$(this).dialog("close");
|
||||
window.location = "/";
|
||||
new instance.web.Dialog(this,{
|
||||
title: 'Import Successful!',
|
||||
buttons: {
|
||||
Ok: function() {
|
||||
this.parents('.modal').modal('hide');
|
||||
window.location = "/";
|
||||
}
|
||||
}
|
||||
}
|
||||
}).html(_t('The document has been successfully imported!'));
|
||||
},$('<div>').html(_t('The document has been successfully imported!'))).open();
|
||||
}
|
||||
},
|
||||
on_imported_error: function(response){
|
||||
|
@ -58,13 +57,12 @@ instance.edi.EdiImport = instance.web.Widget.extend({
|
|||
msg += "\n " + _t("Reason:") + response.data.message;
|
||||
}
|
||||
var params = {error: response, message: msg};
|
||||
$(instance.web.qweb.render("CrashManager.warning", params)).dialog({
|
||||
title: _t("Document Import Notification"),
|
||||
modal: true,
|
||||
buttons: {
|
||||
Ok: function() { $(this).dialog("close"); }
|
||||
}
|
||||
});
|
||||
new instance.web.Dialog(this,{
|
||||
title: _t("Document Import Notification"),
|
||||
buttons: {
|
||||
Ok: function() { this.parents('.modal').modal('hide');}
|
||||
}
|
||||
},$(instance.web.qweb.render("CrashManager.warning", params))).open();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -159,7 +159,11 @@ class event_event(osv.osv):
|
|||
help="The maximum registration level is equal to the sum of the maximum registration of event ticket." +
|
||||
"If you have too much registrations you are not able to confirm your event. (0 to ignore this rule )",
|
||||
type='integer',
|
||||
readonly=True),
|
||||
readonly=True,
|
||||
store={
|
||||
'event.event': (lambda self, cr, uid, ids, c = {}: ids, ['event_ticket_ids'], 20),
|
||||
'event.event.ticket': (_get_ticket_events, ['seats_max'], 10),
|
||||
}),
|
||||
'seats_available': fields.function(Event._get_seats, oldname='register_avail', string='Available Seats',
|
||||
type='integer', multi='seats_reserved',
|
||||
store={
|
||||
|
|
|
@ -333,7 +333,8 @@ class gamification_goal(osv.Model):
|
|||
|
||||
if definition.computation_mode == 'sum':
|
||||
field_name = definition.field_id.name
|
||||
res = obj.read_group(cr, uid, domain, [field_name], [field_name], context=context)
|
||||
# TODO for master: group on user field in batch mode
|
||||
res = obj.read_group(cr, uid, domain, [field_name], [], context=context)
|
||||
new_value = res and res[0][field_name] or 0.0
|
||||
|
||||
else: # computation mode = count
|
||||
|
|
|
@ -141,7 +141,7 @@ class hr_timesheet_sheet(osv.osv):
|
|||
return {
|
||||
sheet_id: {
|
||||
'timesheet_activity_count': Timesheet.search_count(cr,uid, [('sheet_id','=', sheet_id)], context=context),
|
||||
'attendance_count': Attendance.search_count(cr,uid, [('sheed_id', '=', sheet_id)], context=context)
|
||||
'attendance_count': Attendance.search_count(cr,uid, [('sheet_id', '=', sheet_id)], context=context)
|
||||
}
|
||||
for sheet_id in ids
|
||||
}
|
||||
|
|
|
@ -714,7 +714,7 @@ class mail_thread(osv.AbstractModel):
|
|||
s = ', '.join([decode(message.get(h)) for h in header_fields if message.get(h)])
|
||||
return filter(lambda x: x, self._find_partner_from_emails(cr, uid, None, tools.email_split(s), context=context))
|
||||
|
||||
def message_route_verify(self, cr, uid, message, message_dict, route, update_author=True, assert_model=True, create_fallback=True, context=None):
|
||||
def message_route_verify(self, cr, uid, message, message_dict, route, update_author=True, assert_model=True, create_fallback=True, allow_private=False, context=None):
|
||||
""" Verify route validity. Check and rules:
|
||||
1 - if thread_id -> check that document effectively exists; otherwise
|
||||
fallback on a message_new by resetting thread_id
|
||||
|
@ -835,6 +835,9 @@ class mail_thread(osv.AbstractModel):
|
|||
_create_bounce_email()
|
||||
return ()
|
||||
|
||||
if not model and not thread_id and not alias and not allow_private:
|
||||
return ()
|
||||
|
||||
return (model, thread_id, route[2], route[3], route[4])
|
||||
|
||||
def message_route(self, cr, uid, message, message_dict, model=None, thread_id=None,
|
||||
|
@ -886,22 +889,23 @@ class mail_thread(osv.AbstractModel):
|
|||
thread_references = references or in_reply_to
|
||||
|
||||
# 1. message is a reply to an existing message (exact match of message_id)
|
||||
ref_match = thread_references and tools.reference_re.search(thread_references)
|
||||
msg_references = thread_references.split()
|
||||
mail_message_ids = mail_msg_obj.search(cr, uid, [('message_id', 'in', msg_references)], context=context)
|
||||
if mail_message_ids:
|
||||
if ref_match and mail_message_ids:
|
||||
original_msg = mail_msg_obj.browse(cr, SUPERUSER_ID, mail_message_ids[0], context=context)
|
||||
model, thread_id = original_msg.model, original_msg.res_id
|
||||
_logger.info(
|
||||
'Routing mail from %s to %s with Message-Id %s: direct reply to msg: model: %s, thread_id: %s, custom_values: %s, uid: %s',
|
||||
email_from, email_to, message_id, model, thread_id, custom_values, uid)
|
||||
route = self.message_route_verify(
|
||||
cr, uid, message, message_dict,
|
||||
(model, thread_id, custom_values, uid, None),
|
||||
update_author=True, assert_model=True, create_fallback=True, context=context)
|
||||
return route and [route] or []
|
||||
update_author=True, assert_model=False, create_fallback=True, context=context)
|
||||
if route:
|
||||
_logger.info(
|
||||
'Routing mail from %s to %s with Message-Id %s: direct reply to msg: model: %s, thread_id: %s, custom_values: %s, uid: %s',
|
||||
email_from, email_to, message_id, model, thread_id, custom_values, uid)
|
||||
return [route]
|
||||
|
||||
# 2. message is a reply to an existign thread (6.1 compatibility)
|
||||
ref_match = thread_references and tools.reference_re.search(thread_references)
|
||||
if ref_match:
|
||||
reply_thread_id = int(ref_match.group(1))
|
||||
reply_model = ref_match.group(2) or fallback_model
|
||||
|
@ -919,14 +923,15 @@ class mail_thread(osv.AbstractModel):
|
|||
('res_id', '=', thread_id),
|
||||
], context=context)
|
||||
if compat_mail_msg_ids and model_obj.exists(cr, uid, thread_id) and hasattr(model_obj, 'message_update'):
|
||||
_logger.info(
|
||||
'Routing mail from %s to %s with Message-Id %s: direct thread reply (compat-mode) to model: %s, thread_id: %s, custom_values: %s, uid: %s',
|
||||
email_from, email_to, message_id, model, thread_id, custom_values, uid)
|
||||
route = self.message_route_verify(
|
||||
cr, uid, message, message_dict,
|
||||
(model, thread_id, custom_values, uid, None),
|
||||
update_author=True, assert_model=True, create_fallback=True, context=context)
|
||||
return route and [route] or []
|
||||
if route:
|
||||
_logger.info(
|
||||
'Routing mail from %s to %s with Message-Id %s: direct thread reply (compat-mode) to model: %s, thread_id: %s, custom_values: %s, uid: %s',
|
||||
email_from, email_to, message_id, model, thread_id, custom_values, uid)
|
||||
return [route]
|
||||
|
||||
# 2. Reply to a private message
|
||||
if in_reply_to:
|
||||
|
@ -936,12 +941,14 @@ class mail_thread(osv.AbstractModel):
|
|||
], limit=1, context=context)
|
||||
if mail_message_ids:
|
||||
mail_message = mail_msg_obj.browse(cr, uid, mail_message_ids[0], context=context)
|
||||
_logger.info('Routing mail from %s to %s with Message-Id %s: direct reply to a private message: %s, custom_values: %s, uid: %s',
|
||||
email_from, email_to, message_id, mail_message.id, custom_values, uid)
|
||||
route = self.message_route_verify(cr, uid, message, message_dict,
|
||||
(mail_message.model, mail_message.res_id, custom_values, uid, None),
|
||||
update_author=True, assert_model=True, create_fallback=True, context=context)
|
||||
return route and [route] or []
|
||||
update_author=True, assert_model=True, create_fallback=True, allow_private=True, context=context)
|
||||
if route:
|
||||
_logger.info(
|
||||
'Routing mail from %s to %s with Message-Id %s: direct reply to a private message: %s, custom_values: %s, uid: %s',
|
||||
email_from, email_to, message_id, mail_message.id, custom_values, uid)
|
||||
return [route]
|
||||
|
||||
# 3. Look for a matching mail.alias entry
|
||||
# Delivered-To is a safe bet in most modern MTAs, but we have to fallback on To + Cc values
|
||||
|
@ -969,11 +976,12 @@ class mail_thread(osv.AbstractModel):
|
|||
user_id = uid
|
||||
_logger.info('No matching user_id for the alias %s', alias.alias_name)
|
||||
route = (alias.alias_model_id.model, alias.alias_force_thread_id, eval(alias.alias_defaults), user_id, alias)
|
||||
_logger.info('Routing mail from %s to %s with Message-Id %s: direct alias match: %r',
|
||||
email_from, email_to, message_id, route)
|
||||
route = self.message_route_verify(cr, uid, message, message_dict, route,
|
||||
update_author=True, assert_model=True, create_fallback=True, context=context)
|
||||
if route:
|
||||
_logger.info(
|
||||
'Routing mail from %s to %s with Message-Id %s: direct alias match: %r',
|
||||
email_from, email_to, message_id, route)
|
||||
routes.append(route)
|
||||
return routes
|
||||
|
||||
|
@ -987,15 +995,16 @@ class mail_thread(osv.AbstractModel):
|
|||
thread_id = int(thread_id)
|
||||
except:
|
||||
thread_id = False
|
||||
_logger.info('Routing mail from %s to %s with Message-Id %s: fallback to model:%s, thread_id:%s, custom_values:%s, uid:%s',
|
||||
email_from, email_to, message_id, fallback_model, thread_id, custom_values, uid)
|
||||
route = self.message_route_verify(cr, uid, message, message_dict,
|
||||
(fallback_model, thread_id, custom_values, uid, None),
|
||||
update_author=True, assert_model=True, context=context)
|
||||
if route:
|
||||
_logger.info(
|
||||
'Routing mail from %s to %s with Message-Id %s: fallback to model:%s, thread_id:%s, custom_values:%s, uid:%s',
|
||||
email_from, email_to, message_id, fallback_model, thread_id, custom_values, uid)
|
||||
return [route]
|
||||
|
||||
# AssertionError if no routes found and if no bounce occured
|
||||
# ValueError if no routes found and if no bounce occured
|
||||
raise ValueError(
|
||||
'No possible route found for incoming message from %s to %s (Message-Id %s:). '
|
||||
'Create an appropriate mail.alias or force the destination model.' %
|
||||
|
@ -1165,7 +1174,14 @@ class mail_thread(osv.AbstractModel):
|
|||
body = u''
|
||||
if save_original:
|
||||
attachments.append(('original_email.eml', message.as_string()))
|
||||
if not message.is_multipart() or 'text/' in message.get('content-type', ''):
|
||||
|
||||
# Be careful, content-type may contain tricky content like in the
|
||||
# following example so test the MIME type with startswith()
|
||||
#
|
||||
# Content-Type: multipart/related;
|
||||
# boundary="_004_3f1e4da175f349248b8d43cdeb9866f1AMSPR06MB343eurprd06pro_";
|
||||
# type="text/html"
|
||||
if not message.is_multipart() or message.get('content-type', '').startswith("text/"):
|
||||
encoding = message.get_content_charset()
|
||||
body = message.get_payload(decode=True)
|
||||
body = tools.ustr(body, encoding, errors='replace')
|
||||
|
|
|
@ -267,7 +267,10 @@ class mail_compose_message(osv.TransientModel):
|
|||
# mass mailing: rendering override wizard static values
|
||||
if mass_mail_mode and wizard.model:
|
||||
# always keep a copy, reset record name (avoid browsing records)
|
||||
mail_values.update(notification=True, model=wizard.model, res_id=res_id, record_name=False)
|
||||
mail_values.update(notification=True, record_name=False)
|
||||
if hasattr(self.pool[wizard.model], 'message_new'):
|
||||
mail_values['model'] = wizard.model
|
||||
mail_values['res_id'] = res_id
|
||||
# auto deletion of mail_mail
|
||||
if 'mail_auto_delete' in context:
|
||||
mail_values['auto_delete'] = context.get('mail_auto_delete')
|
||||
|
|
|
@ -67,7 +67,8 @@ class MassMailController(http.Controller):
|
|||
|
||||
contact_ids = Contacts.search(cr, SUPERUSER_ID, [('list_id', '=', int(list_id)), ('email', '=', email)], context=context)
|
||||
if not contact_ids:
|
||||
Contacts.name_create(cr, SUPERUSER_ID, email, context=context)
|
||||
contact_ng = Contacts.name_create(cr, SUPERUSER_ID, email, context=context)
|
||||
Contacts.write(cr, SUPERUSER_ID, [contact_ng[0]], {'list_id': int(list_id)}, context=context)
|
||||
# add email to session
|
||||
request.session['mass_mailing_email'] = email
|
||||
return True
|
||||
|
|
|
@ -31,6 +31,63 @@
|
|||
<field name="opt_out" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- Demo newsletter template -->
|
||||
<!--Email template -->
|
||||
<record id="newsletter_template" model="email.template">
|
||||
<field name="name">Newsletter</field>
|
||||
<field name="subject">Newsletter</field>
|
||||
<field name="model_id" ref="mass_mailing.model_mail_mass_mailing_contact"/>
|
||||
<field name="use_default_to" eval="True"/>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
<field name="body_html"><![CDATA[<div data-snippet-id="big-picture" style="padding:0px; margin:0px">
|
||||
<table cellpadding="0" cellspacing="0" style="margin:10px 0px 0px;vertical-align:top;padding:0px;font-family:arial;font-size:12px;color:rgb(51,51,51)">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width:600px" valign="top">
|
||||
<h2 style="text-align: center; padding:0px 5px">A Punchy Headline</h2>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width:600px" valign="top"><img src="/website/static/src/img/big_picture.png" style="display:block;border:none;min-height:250px;margin:0 auto;" width="500"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width:600px" valign="top">
|
||||
<p style="text-align: center; overflow:hidden"></p>
|
||||
|
||||
<h3 style="text-align: center; padding:0px 5px">A Small Subtitle for ${object.name}</h3>
|
||||
|
||||
<p></p>
|
||||
|
||||
<p style="text-align: center; overflow:hidden">Choose a vibrant image and write an inspiring paragraph about it. It does not have to be long, but it should reinforce your image.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div data-snippet-id="three-columns" style="padding:0px; margin:0px">
|
||||
<table cellpadding="0" cellspacing="0" style="margin:10px 0px 0px;vertical-align:top;padding:0px;font-family:arial;font-size:12px;color:rgb(51,51,51)">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width:300px" valign="top"><img src="/website/static/src/img/desert_thumb.jpg" style="display:block;border:none;min-height:50px" width="275"></td>
|
||||
<td style="width:300px" valign="top"><img src="/website/static/src/img/deers_thumb.jpg" style="display:block;border:none;min-height:50px" width="275"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width:300px" valign="top">
|
||||
<h3 style="text-align: center; padding:0px 5px">Feature One</h3>
|
||||
|
||||
<p style="overflow:hidden">Choose a vibrant image and write an inspiring paragraph about it. It does not have to be long, but it should reinforce your image.</p>
|
||||
</td>
|
||||
<td style="width:300px" valign="top">
|
||||
<h3 style="text-align: center; padding:0px 5px">Feature Two</h3>
|
||||
|
||||
<p style="overflow:hidden">Choose a vibrant image and write an inspiring paragraph about it. It does not have to be long, but it should reinforce your image.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>]]></field>
|
||||
</record>
|
||||
|
||||
<!-- Create campaign and mailings -->
|
||||
<record id="mass_mail_category_1" model="mail.mass_mailing.category">
|
||||
<field name="name">Marketing</field>
|
||||
|
|
|
@ -532,8 +532,12 @@ class MassMailing(osv.Model):
|
|||
#------------------------------------------------------
|
||||
|
||||
def get_recipients(self, cr, uid, mailing, context=None):
|
||||
domain = eval(mailing.mailing_domain)
|
||||
res_ids = self.pool[mailing.mailing_model].search(cr, uid, domain, context=context)
|
||||
if mailing.mailing_domain:
|
||||
domain = eval(mailing.mailing_domain)
|
||||
res_ids = self.pool[mailing.mailing_model].search(cr, uid, domain, context=context)
|
||||
else:
|
||||
res_ids = []
|
||||
domain = [('id', 'in', res_ids)]
|
||||
|
||||
# randomly choose a fragment
|
||||
if mailing.contact_ab_pc < 100:
|
||||
|
@ -567,6 +571,7 @@ class MassMailing(osv.Model):
|
|||
'composition_mode': 'mass_mail',
|
||||
'mass_mailing_id': mailing.id,
|
||||
'mailing_list_ids': [(4, l.id) for l in mailing.contact_list_ids],
|
||||
'same_thread': mailing.reply_to_mode == 'thread',
|
||||
}
|
||||
if mailing.reply_to_mode == 'email':
|
||||
composer_values['reply_to'] = mailing.reply_to
|
||||
|
|
|
@ -18,9 +18,12 @@
|
|||
.attr("disabled", data.is_subscriber && data.email.length ? "disabled" : false);
|
||||
self.$target.attr("data-subscribe", data.is_subscriber ? 'on' : 'off');
|
||||
self.$target.find('a.js_subscribe_btn')
|
||||
.val(data.email ? data.email : "")
|
||||
.attr("disabled", data.is_subscriber && data.email.length ? "disabled" : false);
|
||||
self.$target.removeClass("hidden");
|
||||
if (data.is_subscriber) {
|
||||
self.$target.find('.js_subscribe_btn').addClass('hidden');
|
||||
self.$target.find('.js_subscribed_btn').removeClass('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// not if editable mode to allow designer to edit alert field
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
placeholder="your email..."/>
|
||||
<span class="input-group-btn">
|
||||
<a href="#" class="btn btn-primary js_subscribe_btn">Subscribe</a>
|
||||
<a href="#" class="btn btn-success js_subscribed_btn hidden" disabled="disabled">Thanks</a>
|
||||
</span>
|
||||
<div class="alert alert-success hidden">Thanks for your subscription!</div>
|
||||
</div>
|
||||
|
|
|
@ -43,10 +43,15 @@ class MailComposeMessage(osv.TransientModel):
|
|||
}, context=context)
|
||||
mass_mailing = self.pool['mail.mass_mailing'].browse(cr, uid, mass_mailing_id, context=context)
|
||||
for res_id in res_ids:
|
||||
res[res_id]['mailing_id'] = mass_mailing.id
|
||||
res[res_id]['statistics_ids'] = [(0, 0, {
|
||||
'model': wizard.model,
|
||||
'res_id': res_id,
|
||||
'mass_mailing_id': mass_mailing.id,
|
||||
})]
|
||||
res[res_id].update({
|
||||
'mailing_id': mass_mailing.id,
|
||||
'statistics_ids': [(0, 0, {
|
||||
'model': wizard.model,
|
||||
'res_id': res_id,
|
||||
'mass_mailing_id': mass_mailing.id,
|
||||
})],
|
||||
# email-mode: keep original message for routing
|
||||
'notification': mass_mailing.reply_to_mode == 'thread',
|
||||
'auto_delete': True,
|
||||
})
|
||||
return res
|
||||
|
|
|
@ -276,7 +276,8 @@ class procurement_order(osv.osv):
|
|||
@param cr: The current row, from the database cursor,
|
||||
@param uid: The current user ID for security checks
|
||||
@param ids: List of selected IDs
|
||||
@param use_new_cursor: False or the dbname
|
||||
@param use_new_cursor: if set, use a dedicated cursor and auto-commit after processing each procurement.
|
||||
This is appropriate for batch jobs only.
|
||||
@param context: A standard dictionary for contextual values
|
||||
@return: Dictionary of values
|
||||
'''
|
||||
|
@ -284,7 +285,7 @@ class procurement_order(osv.osv):
|
|||
context = {}
|
||||
try:
|
||||
if use_new_cursor:
|
||||
cr = openerp.registry(use_new_cursor).cursor()
|
||||
cr = openerp.registry(cr.dbname).cursor()
|
||||
|
||||
# Run confirmed procurements
|
||||
while True:
|
||||
|
|
|
@ -74,7 +74,7 @@
|
|||
<field name="sequence"/>
|
||||
<field colspan="4" name="name"/>
|
||||
<field name="product_id"/>
|
||||
<field name="product_tmpl_id" groups="product.group_product_variant"/>
|
||||
<field name="product_tmpl_id"/>
|
||||
<field name="categ_id"/>
|
||||
<field name="min_quantity"/>
|
||||
<field name="base"/>
|
||||
|
@ -92,7 +92,7 @@
|
|||
|
||||
<group col="4">
|
||||
<field name="product_id" on_change="product_id_change(product_id)"/>
|
||||
<field name="product_tmpl_id" groups="product.group_product_variant"/>
|
||||
<field name="product_tmpl_id"/>
|
||||
<field name="categ_id"/>
|
||||
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
|
||||
<field name="min_quantity"/>
|
||||
|
|
|
@ -202,6 +202,8 @@ class product_uom(osv.osv):
|
|||
return {}
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
if 'category_id' in vals:
|
||||
for uom in self.browse(cr, uid, ids, context=context):
|
||||
if uom.category_id.id != vals['category_id']:
|
||||
|
@ -344,6 +346,8 @@ class product_attribute_value(osv.osv):
|
|||
return result
|
||||
|
||||
def _set_price_extra(self, cr, uid, id, name, value, args, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
if 'active_id' not in context:
|
||||
return None
|
||||
p_obj = self.pool['product.attribute.price']
|
||||
|
@ -351,7 +355,7 @@ class product_attribute_value(osv.osv):
|
|||
if p_ids:
|
||||
p_obj.write(cr, uid, p_ids, {'price_extra': value}, context=context)
|
||||
else:
|
||||
p_obj.create(cr, uid, p_ids, {
|
||||
p_obj.create(cr, uid, {
|
||||
'product_tmpl_id': context['active_id'],
|
||||
'value_id': id,
|
||||
'price_extra': value,
|
||||
|
@ -458,6 +462,8 @@ class product_template(osv.osv):
|
|||
|
||||
def _set_standard_price(self, cr, uid, product_tmpl_id, value, context=None):
|
||||
''' Store the standard price change in order to be able to retrieve the cost of a product template for a given date'''
|
||||
if context is None:
|
||||
context = {}
|
||||
price_history_obj = self.pool['product.price.history']
|
||||
user_company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
|
||||
company_id = context.get('force_company', user_company)
|
||||
|
@ -533,7 +539,6 @@ class product_template(osv.osv):
|
|||
"resized as a 64x64px image, with aspect ratio preserved. "\
|
||||
"Use this field anywhere a small image is required."),
|
||||
'packaging_ids' : fields.one2many('product.packaging', 'product_tmpl_id', 'Logistical Units',
|
||||
help="Gives the different ways to package the same product. This has no impact on the picking order and is mainly used if you use the EDI module."),
|
||||
'seller_ids': fields.one2many('product.supplierinfo', 'product_tmpl_id', 'Supplier'),
|
||||
'seller_delay': fields.related('seller_ids','delay', type='integer', string='Supplier Lead Time',
|
||||
help="This is the average delay in days between the purchase order confirmation and the reception of goods for this product and for the default supplier. It is used by the scheduler to order requests based on reordering delays."),
|
||||
|
@ -674,7 +679,7 @@ class product_template(osv.osv):
|
|||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
''' Store the standard price change in order to be able to retrieve the cost of a product template for a given date'''
|
||||
if isinstance(id, (int, long)):
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
if 'uom_po_id' in vals:
|
||||
new_uom = self.pool.get('product.uom').browse(cr, uid, vals['uom_po_id'], context=context)
|
||||
|
|
|
@ -213,6 +213,10 @@ FaceTime HD Camera, 1.2 MP Photos</field>
|
|||
<field name="attribute_line_ids" eval="[(6,0,[ref('product.product_attribute_line_1'), ref('product.product_attribute_line_2'), ref('product.product_attribute_line_3')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_product_4d" model="product.product">
|
||||
<field name="active" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record id="product_attribute_price_1" model="product.attribute.price">
|
||||
<field name="product_tmpl_id" ref="product_product_4_product_template"/>
|
||||
<field name="value_id" ref="product_attribute_value_2"/>
|
||||
|
|
|
@ -270,7 +270,7 @@
|
|||
</form>
|
||||
<field name="name" position="replace">
|
||||
<field name="name" attrs="{'invisible': [('id', '!=', False)]}"/>
|
||||
<field name="product_tmpl_id" class="oe_inline" readonly="1" attrs="{'invisible': [('id', '=', False)]}"/>
|
||||
<field name="product_tmpl_id" class="oe_inline" readonly="1" attrs="{'invisible': [('id', '=', False)], 'required': [('id', '!=', False)]}"/>
|
||||
</field>
|
||||
<xpath expr="//div[@class='oe_title']" position="inside">
|
||||
<field name="attribute_value_ids" widget="many2many_tags"/>
|
||||
|
|
|
@ -661,6 +661,8 @@ class purchase_order(osv.osv):
|
|||
_('You must first cancel all invoices related to this purchase order.'))
|
||||
self.pool.get('account.invoice') \
|
||||
.signal_invoice_cancel(cr, uid, map(attrgetter('id'), purchase.invoice_ids))
|
||||
self.pool['purchase.order.line'].write(cr, uid, [l.id for l in purchase.order_line],
|
||||
{'state': 'cancel'})
|
||||
self.write(cr, uid, ids, {'state': 'cancel'})
|
||||
self.set_order_line_status(cr, uid, ids, 'cancel', context=context)
|
||||
self.signal_purchase_cancel(cr, uid, ids)
|
||||
|
@ -982,6 +984,9 @@ class purchase_order_line(osv.osv):
|
|||
return super(purchase_order_line, self).copy_data(cr, uid, id, default, context)
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
for line in self.browse(cr, uid, ids, context=context):
|
||||
if line.state not in ['draft', 'cancel']:
|
||||
raise osv.except_osv(_('Invalid Action!'), _('Cannot delete a purchase order line which is in state \'%s\'.') %(line.state,))
|
||||
procurement_obj = self.pool.get('procurement.order')
|
||||
procurement_ids_to_cancel = procurement_obj.search(cr, uid, [('purchase_line_id', 'in', ids)], context=context)
|
||||
if procurement_ids_to_cancel:
|
||||
|
|
|
@ -538,14 +538,18 @@ class sale_order(osv.osv):
|
|||
if grouped:
|
||||
res = self._make_invoice(cr, uid, val[0][0], reduce(lambda x, y: x + y, [l for o, l in val], []), context=context)
|
||||
invoice_ref = ''
|
||||
origin_ref = ''
|
||||
for o, l in val:
|
||||
invoice_ref += o.name + '|'
|
||||
invoice_ref += (o.client_order_ref or o.name) + '|'
|
||||
origin_ref += (o.origin or o.name) + '|'
|
||||
self.write(cr, uid, [o.id], {'state': 'progress'})
|
||||
cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%s,%s)', (o.id, res))
|
||||
#remove last '|' in invoice_ref
|
||||
if len(invoice_ref) >= 1:
|
||||
if len(invoice_ref) >= 1:
|
||||
invoice_ref = invoice_ref[:-1]
|
||||
invoice.write(cr, uid, [res], {'origin': invoice_ref, 'name': invoice_ref})
|
||||
if len(origin_ref) >= 1:
|
||||
origin_ref = origin_ref[:-1]
|
||||
invoice.write(cr, uid, [res], {'origin': origin_ref, 'name': invoice_ref})
|
||||
else:
|
||||
for order, il in val:
|
||||
res = self._make_invoice(cr, uid, order, il, context=context)
|
||||
|
|
|
@ -126,7 +126,7 @@
|
|||
<div name="options_active"></div>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<group name="left">
|
||||
<field name="user_id" context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'base.group_sale_salesman_all_leads']}"/>
|
||||
<field name="code"/>
|
||||
<field name="parent_id"/>
|
||||
|
|
|
@ -271,7 +271,8 @@ class procurement_order(osv.osv):
|
|||
@param cr: The current row, from the database cursor,
|
||||
@param uid: The current user ID for security checks
|
||||
@param ids: List of selected IDs
|
||||
@param use_new_cursor: False or the dbname
|
||||
@param use_new_cursor: if set, use a dedicated cursor and auto-commit after processing each procurement.
|
||||
This is appropriate for batch jobs only.
|
||||
@param context: A standard dictionary for contextual values
|
||||
@return: Dictionary of values
|
||||
'''
|
||||
|
@ -280,7 +281,7 @@ class procurement_order(osv.osv):
|
|||
context = {}
|
||||
try:
|
||||
if use_new_cursor:
|
||||
cr = openerp.registry(use_new_cursor).cursor()
|
||||
cr = openerp.registry(cr.dbname).cursor()
|
||||
|
||||
move_obj = self.pool.get('stock.move')
|
||||
|
||||
|
@ -333,15 +334,14 @@ class procurement_order(osv.osv):
|
|||
def _procure_orderpoint_confirm(self, cr, uid, use_new_cursor=False, company_id=False, context=None):
|
||||
'''
|
||||
Create procurement based on Orderpoint
|
||||
use_new_cursor: False or the dbname
|
||||
|
||||
@return: Dictionary of values
|
||||
"""
|
||||
:param bool use_new_cursor: if set, use a dedicated cursor and auto-commit after processing each procurement.
|
||||
This is appropriate for batch jobs only.
|
||||
'''
|
||||
if context is None:
|
||||
context = {}
|
||||
if use_new_cursor:
|
||||
cr = openerp.registry(use_new_cursor).db.cursor()
|
||||
cr = openerp.registry(cr.dbname).db.cursor()
|
||||
orderpoint_obj = self.pool.get('stock.warehouse.orderpoint')
|
||||
|
||||
procurement_obj = self.pool.get('procurement.order')
|
||||
|
|
|
@ -2276,6 +2276,9 @@ class stock_move(osv.osv):
|
|||
|
||||
#Check moves that were pushed
|
||||
if move.move_dest_id.state in ('waiting', 'confirmed'):
|
||||
# FIXME is opw 607970 still present with new WMS?
|
||||
# (see commits 1ef2c181033bd200906fb1e5ce35e234bf566ac6
|
||||
# and 41c5ceb8ebb95c1b4e98d8dd1f12b8e547a24b1d)
|
||||
other_upstream_move_ids = self.search(cr, uid, [('id', '!=', move.id), ('state', 'not in', ['done', 'cancel']),
|
||||
('move_dest_id', '=', move.move_dest_id.id)], context=context)
|
||||
#If no other moves for the move that got pushed:
|
||||
|
|
|
@ -779,9 +779,9 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({
|
|||
});
|
||||
var am = instance.webclient.action_manager;
|
||||
var form = am.dialog_widget.views.form.controller;
|
||||
form.on("on_button_cancel", am.dialog, am.dialog.close);
|
||||
form.on("on_button_cancel", am.dialog, function() { return am.dialog.$dialog_box.modal('hide'); });
|
||||
form.on('record_saved', self, function() {
|
||||
am.dialog.close();
|
||||
am.dialog.$dialog_box.modal('hide');
|
||||
self.view.do_reload();
|
||||
});
|
||||
},
|
||||
|
|
|
@ -17,3 +17,8 @@
|
|||
transform-origin: 0 0;
|
||||
margin: 0 0px -300px 0;
|
||||
}
|
||||
|
||||
.o_mail_body {
|
||||
width: 620px;
|
||||
margin-left: 16px;
|
||||
}
|
|
@ -14,6 +14,9 @@
|
|||
$('#email_designer').show();
|
||||
$('#email_template').hide();
|
||||
$(".js_content", $(this).parent()).children().clone().appendTo('#email_body');
|
||||
$(".js_content", $(this).parent()).children().clone().appendTo('#email_body_html');
|
||||
$('#email_body').addClass('oe_dirty');
|
||||
$('#email_body_html').addClass('oe_dirty');
|
||||
|
||||
openerp.website.editor_bar.edit();
|
||||
event.preventDefault();
|
||||
|
|
|
@ -64,11 +64,11 @@
|
|||
</div>
|
||||
<hr/>
|
||||
<!-- body fields -->
|
||||
<div t-if="body_field == 'body_html'">
|
||||
<div t-field="record.body_html" id="email_body_html"/>
|
||||
<div t-if="body_field == 'body_html'" class="col-sm-offset-2">
|
||||
<div t-field="record.body_html" id="email_body_html" class="o_mail_body"/>
|
||||
</div>
|
||||
<div t-if="body_field == 'body'">
|
||||
<div t-field="record.body" id="email_body"/>
|
||||
<div t-if="body_field == 'body'" class="col-sm-offset-2">
|
||||
<div t-field="record.body" id="email_body" class="o_mail_body"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp import tools
|
||||
from openerp.osv import osv, fields
|
||||
|
||||
class product_style(osv.Model):
|
||||
|
|
|
@ -289,6 +289,13 @@
|
|||
.css_attribute_color input {
|
||||
margin: 8px;
|
||||
height: 13px;
|
||||
opacity: 0;
|
||||
}
|
||||
.css_attribute_color.active {
|
||||
border: 3px ridge #66ee66;
|
||||
}
|
||||
.css_attribute_color.active input {
|
||||
margin: 6px;
|
||||
}
|
||||
|
||||
.js_add_cart_variants option.css_not_available {
|
||||
|
|
|
@ -251,6 +251,12 @@
|
|||
input
|
||||
margin: 8px
|
||||
height: 13px
|
||||
opacity: 0
|
||||
&.active
|
||||
border: 3px ridge #66ee66
|
||||
&.active input
|
||||
margin: 6px
|
||||
|
||||
.js_add_cart_variants
|
||||
option.css_not_available
|
||||
color: #ccc
|
||||
|
|
|
@ -62,6 +62,11 @@ $(document).ready(function () {
|
|||
var dec = value % 1;
|
||||
$price.html(value + (dec < 0.01 ? ".00" : (dec < 1 ? "0" : "") ));
|
||||
});
|
||||
// hightlight selected color
|
||||
$('.css_attribute_color input').on('change', function (ev) {
|
||||
$('.css_attribute_color').removeClass("active");
|
||||
$('.css_attribute_color:has(input:checked)').addClass("active");
|
||||
});
|
||||
|
||||
var $form_var = $('form.js_add_cart_variants');
|
||||
var variant_ids = $form_var.data("attribute_value_ids");
|
||||
|
|
|
@ -297,8 +297,8 @@
|
|||
</t>
|
||||
<t t-if="a.type == 'color'">
|
||||
<t t-foreach="a.value_ids" t-as="v">
|
||||
<label class="css_attribute_color"
|
||||
t-attf-style="background-color:#{v.color or v.name}">
|
||||
<label t-attf-style="background-color:#{v.color or v.name}"
|
||||
t-attf-class="css_attribute_color #{'active' if v.id in attrib_set else ''}">
|
||||
<input type="checkbox"
|
||||
name="attrib"
|
||||
t-att-value="'%s,%s' % (a.id,v.id)"
|
||||
|
@ -481,8 +481,8 @@
|
|||
<ul class="nav nav-pills nav-stacked">
|
||||
<t t-set="inc" t-value="0"/>
|
||||
<t t-foreach="variant_id.value_ids" t-as="value_id">
|
||||
<label class="css_attribute_color"
|
||||
t-attf-style="background-color:#{value_id.color or value_id.name}">
|
||||
<label t-attf-style="background-color:#{value_id.color or value_id.name}"
|
||||
t-attf-class="css_attribute_color #{'active' if not inc else ''}">
|
||||
<input type="radio"
|
||||
t-att-checked="'checked' if not inc else ''"
|
||||
t-att-name="'attribute-%s' % variant_id.attribute_id.id"
|
||||
|
@ -529,13 +529,12 @@
|
|||
|
||||
<template id="product_attributes" inherit_id="website_sale.product" optional="enabled" name="Product attributes">
|
||||
<xpath expr="//p[@t-field='product.description_sale']" position="after">
|
||||
<hr t-if="product.attribute_line_ids"/>
|
||||
<hr t-if="sum([(1 if len(l.value_ids)==1 else 0) for l in product.attribute_line_ids])"/>
|
||||
<p class="text-muted">
|
||||
<t t-foreach="product.attribute_line_ids" t-as="variant_id">
|
||||
<t t-set="inc" t-value="0"/>
|
||||
<span t-field="variant_id.attribute_id"/>:
|
||||
<t t-foreach="variant_id.value_ids" t-as="value_id"><t t-if="inc">,</t> <span t-field="value_id.name"/><t t-set="inc" t-value="inc+1"/></t>
|
||||
<br/>
|
||||
<t t-if="len(variant_id.value_ids)==1">
|
||||
<span t-field="variant_id.attribute_id"/>: <span t-field="variant_id.value_ids[0].name"/><br/>
|
||||
</t>
|
||||
</t>
|
||||
</p>
|
||||
</xpath>
|
||||
|
|
|
@ -108,6 +108,7 @@
|
|||
"access_multi_company_default manager","multi_company_default Manager","model_multi_company_default","group_erp_manager",1,1,1,1
|
||||
"access_ir_filter all","ir_filters all","model_ir_filters",,1,1,1,1
|
||||
"access_ir_config_parameter","ir_config_parameter","model_ir_config_parameter",,1,0,0,0
|
||||
"access_ir_config_parameter_system","ir_config_parameter_system","model_ir_config_parameter","group_system",1,1,1,1
|
||||
"access_ir_mail_server","ir_mail_server","model_ir_mail_server","group_system",1,1,1,1
|
||||
"access_ir_actions_client","ir_actions_client all","model_ir_actions_client",,1,0,0,0
|
||||
"access_ir_needaction_mixin","ir_needaction_mixin","model_ir_needaction_mixin",,1,1,1,1
|
||||
|
|
|
|
@ -665,35 +665,49 @@ class EndPoint(object):
|
|||
|
||||
def routing_map(modules, nodb_only, converters=None):
|
||||
routing_map = werkzeug.routing.Map(strict_slashes=False, converters=converters)
|
||||
|
||||
def get_subclasses(klass):
|
||||
def valid(c):
|
||||
return c.__module__.startswith('openerp.addons.') and c.__module__.split(".")[2] in modules
|
||||
subclasses = klass.__subclasses__()
|
||||
result = []
|
||||
for subclass in subclasses:
|
||||
if valid(subclass):
|
||||
result.extend(get_subclasses(subclass))
|
||||
if not result and valid(klass):
|
||||
result = [klass]
|
||||
return result
|
||||
|
||||
uniq = lambda it: collections.OrderedDict((id(x), x) for x in it).values()
|
||||
|
||||
for module in modules:
|
||||
if module not in controllers_per_module:
|
||||
continue
|
||||
|
||||
for _, cls in controllers_per_module[module]:
|
||||
subclasses = cls.__subclasses__()
|
||||
subclasses = [c for c in subclasses if c.__module__.startswith('openerp.addons.') and c.__module__.split(".")[2] in modules]
|
||||
subclasses = uniq(c for c in get_subclasses(cls) if c is not cls)
|
||||
if subclasses:
|
||||
name = "%s (extended by %s)" % (cls.__name__, ', '.join(sub.__name__ for sub in subclasses))
|
||||
cls = type(name, tuple(reversed(subclasses)), {})
|
||||
|
||||
o = cls()
|
||||
members = inspect.getmembers(o)
|
||||
for mk, mv in members:
|
||||
if inspect.ismethod(mv) and hasattr(mv, 'routing'):
|
||||
members = inspect.getmembers(o, inspect.ismethod)
|
||||
for _, mv in members:
|
||||
if hasattr(mv, 'routing'):
|
||||
routing = dict(type='http', auth='user', methods=None, routes=None)
|
||||
methods_done = list()
|
||||
routing_type = None
|
||||
# update routing attributes from subclasses(auth, methods...)
|
||||
for claz in reversed(mv.im_class.mro()):
|
||||
fn = getattr(claz, mv.func_name, None)
|
||||
if fn and hasattr(fn, 'routing') and fn not in methods_done:
|
||||
methods_done.append(fn)
|
||||
routing.update(fn.routing)
|
||||
if not nodb_only or nodb_only == (routing['auth'] == "none"):
|
||||
if not nodb_only or routing['auth'] == "none":
|
||||
assert routing['routes'], "Method %r has not route defined" % mv
|
||||
endpoint = EndPoint(mv, routing)
|
||||
for url in routing['routes']:
|
||||
if routing.get("combine", False):
|
||||
# deprecated
|
||||
# deprecated v7 declaration
|
||||
url = o._cp_path.rstrip('/') + '/' + url.lstrip('/')
|
||||
if url.endswith("/") and len(url) > 1:
|
||||
url = url[: -1]
|
||||
|
|
|
@ -2449,7 +2449,7 @@ class BaseModel(object):
|
|||
fetched_data = cr.dictfetchall()
|
||||
|
||||
if not groupby_fields:
|
||||
return {r.pop('id'): r for r in fetched_data}
|
||||
return fetched_data
|
||||
|
||||
many2onefields = [gb['field'] for gb in annotated_groupbys if gb['type'] == 'many2one']
|
||||
if many2onefields:
|
||||
|
|
Loading…
Reference in New Issue