summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2017-04-25 12:36:02 +0200
committerChristophe Simonis <chs@odoo.com>2017-04-25 12:36:02 +0200
commit4441237b0a80cc8eca04a35ad825bce876a2d7b6 (patch)
treeefafd204a16cc8fd762ac24f3af4ddf90ae57d6e
parent38bb4f07d0bfb0de12414ab99c4815c0d9ff48a6 (diff)
parentab68a1c6740162c59fa54effc6133964d0a51bc8 (diff)
[MERGE] forward port branch saas-15 up to ab68a1c6740
-rw-r--r--addons/account/report/account_overdue_report.py2
-rw-r--r--addons/calendar/models/calendar.py1
-rw-r--r--addons/crm/models/crm_team.py2
-rw-r--r--addons/crm/wizard/base_partner_merge.py10
-rw-r--r--addons/event/models/event.py2
-rw-r--r--addons/hr_maintenance/views/maintenance_views.xml16
-rw-r--r--addons/mail/models/mail_mail.py4
-rw-r--r--addons/marketing_campaign/models/marketing_campaign.py2
-rw-r--r--addons/mass_mailing/models/mass_mailing.py8
-rw-r--r--addons/mass_mailing/views/mass_mailing_views.xml3
-rw-r--r--addons/mrp/models/stock_move.py9
-rw-r--r--addons/payment_paypal/controllers/main.py2
-rw-r--r--addons/product/models/product_attribute.py2
-rw-r--r--addons/sale/models/sale.py3
-rw-r--r--addons/stock/models/stock_location.py18
-rw-r--r--addons/stock/models/stock_quant.py2
-rw-r--r--addons/web/controllers/main.py11
-rw-r--r--addons/web/static/lib/qweb/qweb2.js5
-rw-r--r--addons/web/views/database_manager.html6
-rw-r--r--addons/website_blog/controllers/main.py2
-rw-r--r--addons/website_theme_install/views/views.xml1
-rw-r--r--doc/cla/corporate/levelprime.md17
-rw-r--r--odoo/addons/base/ir/ir_mail_server.py8
-rw-r--r--odoo/addons/base/module/module_data.xml2
-rw-r--r--odoo/cli/start.py3
25 files changed, 104 insertions, 37 deletions
diff --git a/addons/account/report/account_overdue_report.py b/addons/account/report/account_overdue_report.py
index 0fb7529e20b..fd032197233 100644
--- a/addons/account/report/account_overdue_report.py
+++ b/addons/account/report/account_overdue_report.py
@@ -25,7 +25,7 @@ class ReportOverdue(models.AbstractModel):
"FROM account_move_line l "
"JOIN account_account_type at ON (l.user_type_id = at.id) "
"JOIN account_move m ON (l.move_id = m.id) "
- "WHERE l.partner_id IN %s AND at.type IN ('receivable', 'payable') GROUP BY l.date, l.name, l.ref, l.date_maturity, l.partner_id, at.type, l.blocked, l.amount_currency, l.currency_id, l.move_id, m.name", (((fields.date.today(), ) + (tuple(partner_ids),))))
+ "WHERE l.partner_id IN %s AND at.type IN ('receivable', 'payable') AND NOT l.reconciled GROUP BY l.date, l.name, l.ref, l.date_maturity, l.partner_id, at.type, l.blocked, l.amount_currency, l.currency_id, l.move_id, m.name", (((fields.date.today(), ) + (tuple(partner_ids),))))
for row in self.env.cr.dictfetchall():
res[row.pop('partner_id')].append(row)
return res
diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py
index 70f25179465..f4380d12e66 100644
--- a/addons/calendar/models/calendar.py
+++ b/addons/calendar/models/calendar.py
@@ -170,6 +170,7 @@ class Attendee(models.Model):
vals = {}
if ics_file:
vals['attachment_ids'] = [(0, 0, {'name': 'invitation.ics',
+ 'mimetype': 'text/calendar',
'datas_fname': 'invitation.ics',
'datas': str(ics_file).encode('base64')})]
vals['model'] = None # We don't want to have the mail in the tchatter while in queue!
diff --git a/addons/crm/models/crm_team.py b/addons/crm/models/crm_team.py
index aa6ec04747f..f06be180ab3 100644
--- a/addons/crm/models/crm_team.py
+++ b/addons/crm/models/crm_team.py
@@ -130,7 +130,7 @@ class Team(models.Model):
@api.multi
def write(self, vals):
result = super(Team, self).write(vals)
- if vals.get('use_leads') or vals.get('alias_defaults'):
+ if 'use_leads' in vals or 'alias_defaults' in vals:
for team in self:
team.alias_id.write(team.get_alias_values())
return result
diff --git a/addons/crm/wizard/base_partner_merge.py b/addons/crm/wizard/base_partner_merge.py
index 18fd4c6a3e6..27f3f3e002d 100644
--- a/addons/crm/wizard/base_partner_merge.py
+++ b/addons/crm/wizard/base_partner_merge.py
@@ -489,13 +489,19 @@ class MergePartnerAutomatic(models.TransientModel):
counter = 0
for min_id, aggr_ids in self._cr.fetchall():
+ # To ensure that the used partners are accessible by the user
+ partners = self.env['res.partner'].search([('id', 'in', aggr_ids)])
+ if len(partners) < 2:
+ continue
+
# exclude partner according to options
- if model_mapping and self._partner_use_in(aggr_ids, model_mapping):
+ if model_mapping and self._partner_use_in(partners.ids, model_mapping):
continue
+
self.env['base.partner.merge.line'].create({
'wizard_id': self.id,
'min_id': min_id,
- 'aggr_ids': aggr_ids,
+ 'aggr_ids': partners.ids,
})
counter += 1
diff --git a/addons/event/models/event.py b/addons/event/models/event.py
index 4e7a144eac3..de8117e0cee 100644
--- a/addons/event/models/event.py
+++ b/addons/event/models/event.py
@@ -30,7 +30,7 @@ class EventType(models.Model):
'interval_unit': 'days',
'interval_type': 'before_event',
'template_id': self.env.ref('event.event_reminder')
- })] if self.user_has_groups('event.group_email_scheduling') else []
+ })]
name = fields.Char('Event Category', required=True, translate=True)
# registration
diff --git a/addons/hr_maintenance/views/maintenance_views.xml b/addons/hr_maintenance/views/maintenance_views.xml
index 2c56a9f2538..72970f9cd77 100644
--- a/addons/hr_maintenance/views/maintenance_views.xml
+++ b/addons/hr_maintenance/views/maintenance_views.xml
@@ -54,6 +54,20 @@
</record>
<!-- equiment : views -->
+ <record id="maintenance_equipment_view_search_inherit_hr" model='ir.ui.view'>
+ <field name="name">maintenance.equipment.view.search.inherit.hr</field>
+ <field name="model">maintenance.equipment</field>
+ <field name="inherit_id" ref="maintenance.hr_equipment_view_search"/>
+ <field name="arch" type="xml">
+ <filter name="assigned" position="attributes">
+ <attribute name="domain">['|', ('employee_id', '!=', False), ('department_id', '!=', False)]</attribute>
+ </filter>
+ <filter name="available" position="attributes">
+ <attribute name="domain">[('employee_id', '=', False), ('department_id', '=', False)]</attribute>
+ </filter>
+ </field>
+ </record>
+
<record id="maintenance_equipment_view_form_inherit_hr" model="ir.ui.view">
<field name="name">maintenance.equipment.view.form.inherit.hr</field>
<field name="model">maintenance.equipment</field>
@@ -99,4 +113,4 @@
</xpath>
</field>
</record>
-</odoo> \ No newline at end of file
+</odoo>
diff --git a/addons/mail/models/mail_mail.py b/addons/mail/models/mail_mail.py
index ff25c60f04a..3ac2285fab3 100644
--- a/addons/mail/models/mail_mail.py
+++ b/addons/mail/models/mail_mail.py
@@ -270,8 +270,8 @@ class MailMail(models.Model):
# load attachment binary data with a separate read(), as prefetching all
# `datas` (binary field) could bloat the browse cache, triggerring
# soft/hard mem limits with temporary data.
- attachments = [(a['datas_fname'], base64.b64decode(a['datas']))
- for a in mail.attachment_ids.sudo().read(['datas_fname', 'datas'])]
+ attachments = [(a['datas_fname'], base64.b64decode(a['datas']), a['mimetype'])
+ for a in mail.attachment_ids.sudo().read(['datas_fname', 'datas', 'mimetype'])]
# specific behavior to customize the send email for notified partners
email_list = []
diff --git a/addons/marketing_campaign/models/marketing_campaign.py b/addons/marketing_campaign/models/marketing_campaign.py
index b6f8ba64b48..de60ed86e4f 100644
--- a/addons/marketing_campaign/models/marketing_campaign.py
+++ b/addons/marketing_campaign/models/marketing_campaign.py
@@ -426,7 +426,7 @@ class MarketingCampaignWorkitem(models.Model):
campaign_id = fields.Many2one('marketing.campaign', related='activity_id.campaign_id', string='Campaign', readonly=True, store=True)
object_id = fields.Many2one('ir.model', related='activity_id.campaign_id.object_id', string='Resource', index=1, readonly=True, store=True)
res_id = fields.Integer('Resource ID', index=1, readonly=True)
- res_name = fields.Char(compute='_compute_res_name', string='Resource Name', search='search_res_name')
+ res_name = fields.Char(compute='_compute_res_name', string='Resource Name', search='_search_res_name')
date = fields.Datetime('Execution Date', readonly=True, default=False,
help='If date is not set, this workitem has to be run manually')
partner_id = fields.Many2one('res.partner', 'Partner', index=1, readonly=True)
diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py
index df633272835..cc3519ef737 100644
--- a/addons/mass_mailing/models/mass_mailing.py
+++ b/addons/mass_mailing/models/mass_mailing.py
@@ -153,10 +153,10 @@ class MassMailingCampaign(models.Model):
mass_mailing_ids = fields.One2many(
'mail.mass_mailing', 'mass_mailing_campaign_id',
string='Mass Mailings')
- unique_ab_testing = fields.Boolean(string='AB Testing',
- help='If checked, recipients will be mailed only once, allowing to send '
- 'various mailings in a single campaign to test the effectiveness '
- 'of the mailings.')
+ unique_ab_testing = fields.Boolean(string='Allow A/B Testing', default=True,
+ help='If checked, recipients will be mailed only once for the whole campaign. '
+ 'This lets you send different mailings to randomly selected recipients and test '
+ 'the effectiveness of the mailings, without causing duplicate messages.')
color = fields.Integer(string='Color Index')
clicks_ratio = fields.Integer(compute="_compute_clicks_ratio", string="Number of clicks")
# stat fields
diff --git a/addons/mass_mailing/views/mass_mailing_views.xml b/addons/mass_mailing/views/mass_mailing_views.xml
index 67f8bd7240b..6dc48798c86 100644
--- a/addons/mass_mailing/views/mass_mailing_views.xml
+++ b/addons/mass_mailing/views/mass_mailing_views.xml
@@ -142,7 +142,7 @@
<field name="view_type">form</field>
<field name="view_mode">tree,form,graph</field>
<field name="domain">[('list_ids', 'in', [active_id])]</field>
- <field name="context">{'search_default_not_opt_out': 1}</field>
+ <field name="context">{'search_default_not_opt_out': 1, 'default_list_ids': [(4,active_id)]}</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a recipient.
@@ -691,6 +691,7 @@
<field name="name" string="Campaign Name"/>
<field name="user_id"/>
<field name="tag_ids" widget="many2many_tags" options="{'no_create_edit': True}"/>
+ <field name="unique_ab_testing" groups="base.group_no_one"/>
</group>
<notebook>
<page string="Tracking">
diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py
index ad1a50ad738..1fee94e7f5f 100644
--- a/addons/mrp/models/stock_move.py
+++ b/addons/mrp/models/stock_move.py
@@ -386,3 +386,12 @@ class StockMove(models.Model):
'split_from': self.id, # Needed in order to keep sale connection, but will be removed by unlink
})
return self.env['stock.move']
+
+class PushedFlow(models.Model):
+ _inherit = "stock.location.path"
+
+ def _prepare_move_copy_values(self, move_to_copy, new_date):
+ new_move_vals = super(PushedFlow, self)._prepare_move_copy_values(move_to_copy, new_date)
+ new_move_vals['production_id'] = False
+
+ return new_move_vals
diff --git a/addons/payment_paypal/controllers/main.py b/addons/payment_paypal/controllers/main.py
index 2fefb1417ef..00359c4d038 100644
--- a/addons/payment_paypal/controllers/main.py
+++ b/addons/payment_paypal/controllers/main.py
@@ -23,7 +23,7 @@ class PaypalController(http.Controller):
""" Extract the return URL from the data coming from paypal. """
return_url = post.pop('return_url', '')
if not return_url:
- custom = json.loads(post.pop('custom', False) or post.pop('cm', False) or '{}')
+ custom = json.loads(urllib.unquote_plus(post.pop('custom', False) or post.pop('cm', False) or '{}'))
return_url = custom.get('return_url', '/')
return return_url
diff --git a/addons/product/models/product_attribute.py b/addons/product/models/product_attribute.py
index 9c9a8c09388..bdedd9d7f9d 100644
--- a/addons/product/models/product_attribute.py
+++ b/addons/product/models/product_attribute.py
@@ -76,7 +76,7 @@ class ProductAttributevalue(models.Model):
@api.multi
def _variant_name(self, variable_attributes):
- return ", ".join([v.name for v in self if v.attribute_id in variable_attributes])
+ return ", ".join([v.name for v in self.sorted(key=lambda r: r.name) if v.attribute_id in variable_attributes])
class ProductAttributePrice(models.Model):
diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py
index a30cb13aa78..12d93931bc8 100644
--- a/addons/sale/models/sale.py
+++ b/addons/sale/models/sale.py
@@ -347,7 +347,7 @@ class SaleOrder(models.Model):
vals = {}
if order.name not in invoices[group_key].origin.split(', '):
vals['origin'] = invoices[group_key].origin + ', ' + order.name
- if order.client_order_ref and order.client_order_ref not in invoices[group_key].name.split(', '):
+ if order.client_order_ref and order.client_order_ref not in invoices[group_key].name.split(', ') and order.client_order_ref != invoices[group_key].name:
vals['name'] = invoices[group_key].name + ', ' + order.client_order_ref
invoices[group_key].write(vals)
if line.qty_to_invoice > 0:
@@ -822,7 +822,6 @@ class SaleOrderLine(models.Model):
'uom_id': self.product_uom.id,
'product_id': self.product_id.id or False,
'layout_category_id': self.layout_category_id and self.layout_category_id.id or False,
- 'product_id': self.product_id.id or False,
'invoice_line_tax_ids': [(6, 0, self.tax_id.ids)],
'account_analytic_id': self.order_id.project_id.id,
'analytic_tag_ids': [(6, 0, self.analytic_tag_ids.ids)],
diff --git a/addons/stock/models/stock_location.py b/addons/stock/models/stock_location.py
index 17f8816b6c6..540bd6f1a06 100644
--- a/addons/stock/models/stock_location.py
+++ b/addons/stock/models/stock_location.py
@@ -213,9 +213,15 @@ class PushedFlow(models.Model):
# TDE FIXME: should probably be done in the move model IMO
move._push_apply()
else:
- new_move = move.copy({
- 'origin': move.origin or move.picking_id.name or "/",
- 'location_id': move.location_dest_id.id,
+ new_move_vals = self._prepare_move_copy_values(move, new_date)
+ new_move = move.copy(new_move_vals)
+ move.write({'move_dest_id': new_move.id})
+ new_move.action_confirm()
+
+ def _prepare_move_copy_values(self, move_to_copy, new_date):
+ new_move_vals = {
+ 'origin': move_to_copy.origin or move_to_copy.picking_id.name or "/",
+ 'location_id': move_to_copy.location_dest_id.id,
'location_dest_id': self.location_dest_id.id,
'date': new_date,
'date_expected': new_date,
@@ -226,6 +232,6 @@ class PushedFlow(models.Model):
'push_rule_id': self.id,
'warehouse_id': self.warehouse_id.id,
'procurement_id': False,
- })
- move.write({'move_dest_id': new_move.id})
- new_move.action_confirm()
+ }
+
+ return new_move_vals
diff --git a/addons/stock/models/stock_quant.py b/addons/stock/models/stock_quant.py
index 9c95a6682ac..1d144695f52 100644
--- a/addons/stock/models/stock_quant.py
+++ b/addons/stock/models/stock_quant.py
@@ -520,7 +520,7 @@ class Quant(models.Model):
if any(quant not in self for quant in package.get_content()):
all_in = False
if all_in:
- destinations = [product_to_location[product] for product in package.get_content().mapped('product_id')]
+ destinations = set([product_to_location[product] for product in package.get_content().mapped('product_id')])
if len(destinations) > 1:
all_in = False
if all_in:
diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py
index 8bc312eee98..ef9e52cdf1d 100644
--- a/addons/web/controllers/main.py
+++ b/addons/web/controllers/main.py
@@ -54,6 +54,8 @@ env.filters["json"] = json.dumps
# 1 week cache for asset bundles as advised by Google Page Speed
BUNDLE_MAXAGE = 60 * 60 * 24 * 7
+DBNAME_PATTERN = '^[a-zA-Z0-9][a-zA-Z0-9_.-]+$'
+
#----------------------------------------------------------
# Odoo Web helpers
#----------------------------------------------------------
@@ -641,6 +643,7 @@ class Database(http.Controller):
d['list_db'] = odoo.tools.config['list_db']
d['langs'] = odoo.service.db.exp_list_lang()
d['countries'] = odoo.service.db.exp_list_countries()
+ d['pattern'] = DBNAME_PATTERN
# databases list
d['databases'] = []
try:
@@ -665,8 +668,8 @@ class Database(http.Controller):
@http.route('/web/database/create', type='http', auth="none", methods=['POST'], csrf=False)
def create(self, master_pwd, name, lang, password, **post):
try:
- if not re.match('^[a-zA-Z][a-zA-Z0-9_]+$', name):
- raise Exception(_('Invalid database name. Only alphanumerical characters and underscore are allowed.'))
+ if not re.match(DBNAME_PATTERN, name):
+ raise Exception(_('Invalid database name. Only alphanumerical characters, underscore, hyphen and dot are allowed.'))
# country code could be = "False" which is actually True in python
country_code = post.get('country_code') or False
dispatch_rpc('db', 'create_database', [master_pwd, name, bool(post.get('demo')), lang, password, post['login'], country_code])
@@ -679,8 +682,8 @@ class Database(http.Controller):
@http.route('/web/database/duplicate', type='http', auth="none", methods=['POST'], csrf=False)
def duplicate(self, master_pwd, name, new_name):
try:
- if not re.match('^[a-zA-Z][a-zA-Z0-9_]+$', new_name):
- raise Exception(_('Invalid database name. Only alphanumerical characters and underscore are allowed.'))
+ if not re.match(DBNAME_PATTERN, new_name):
+ raise Exception(_('Invalid database name. Only alphanumerical characters, underscore, hyphen and dot are allowed.'))
dispatch_rpc('db', 'duplicate_database', [master_pwd, name, new_name])
return http.local_redirect('/web/database/manager')
except Exception as e:
diff --git a/addons/web/static/lib/qweb/qweb2.js b/addons/web/static/lib/qweb/qweb2.js
index 1e854a6960c..378c5eb6023 100644
--- a/addons/web/static/lib/qweb/qweb2.js
+++ b/addons/web/static/lib/qweb/qweb2.js
@@ -129,12 +129,15 @@ var QWeb2 = {
}
return r.join('');
} else {
+ // avoid XMLSerializer with text node for IE
+ if (node.nodeType == 3) {
+ return node.data;
+ }
if (typeof XMLSerializer !== 'undefined') {
return (new XMLSerializer()).serializeToString(node);
} else {
switch(node.nodeType) {
case 1: return node.outerHTML;
- case 3: return node.data;
case 4: return '<![CDATA[' + node.data + ']]>';
case 8: return '<!-- ' + node.data + '-->';
}
diff --git a/addons/web/views/database_manager.html b/addons/web/views/database_manager.html
index 7f91bc73907..7f0d2fc04ff 100644
--- a/addons/web/views/database_manager.html
+++ b/addons/web/views/database_manager.html
@@ -60,7 +60,7 @@
<div class="row">
<div class="col-md-12">
<label for="name" class="control-label">Database Name</label>
- <input id="name" type="text" name="name" class="form-control" required="required" autocomplete="off" pattern="^[a-zA-Z][a-zA-Z0-9_]+$" title="Only alphanumerical characters and underscore are allowed"/>
+ <input id="name" type="text" name="name" class="form-control" required="required" autocomplete="off" pattern="{{ pattern }}" title="Only alphanumerical characters, underscore, hyphen and dot are allowed"/>
</div>
</div>
</div>
@@ -210,7 +210,7 @@
</div>
<div class="form-group">
<label for="name" class="control-label">Database Name</label>
- <input id="name" type="text" name="name" class="form-control" required="required" pattern="^[a-zA-Z][a-zA-Z0-9_]+$" title="Only alphanumerical characters and underscore are allowed"/>
+ <input id="name" type="text" name="name" class="form-control" required="required" pattern="{{ pattern }}" title="Only alphanumerical characters, underscore, hyphen and dot are allowed"/>
</div>
<div class="form-group">
<label for="copy">This database might have been moved or copied.</label>
@@ -283,7 +283,7 @@
</div>
<div class="form-group">
<label for="new_name" class="control-label">New Name</label>
- <input id="new_name" type="text" name="new_name" class="form-control" required="required" pattern="^[a-zA-Z][a-zA-Z0-9_]+$" title="Only alphanumerical characters and underscore are allowed"/>
+ <input id="new_name" type="text" name="new_name" class="form-control" required="required" pattern="{{ pattern }}" title="Only alphanumerical characters, underscore, hyphen and dot are allowed"/>
</div>
</div>
<div class="modal-footer">
diff --git a/addons/website_blog/controllers/main.py b/addons/website_blog/controllers/main.py
index 4f36967ba76..6ae960fc9c9 100644
--- a/addons/website_blog/controllers/main.py
+++ b/addons/website_blog/controllers/main.py
@@ -318,7 +318,7 @@ class WebsiteBlog(http.Controller):
:return redirect to the new blog created
"""
- new_blog_post = request.env['blog.post'].with_context(mail_create_nosubscribe=True).copy(int(blog_post_id), {})
+ new_blog_post = request.env['blog.post'].with_context(mail_create_nosubscribe=True).browse(int(blog_post_id)).copy()
return werkzeug.utils.redirect("/blog/%s/post/%s?enable_editor=1" % (slug(new_blog_post.blog_id), slug(new_blog_post)))
@http.route('/blog/post_get_discussion/', type='json', auth="public", website=True)
diff --git a/addons/website_theme_install/views/views.xml b/addons/website_theme_install/views/views.xml
index 6128d5b2d51..6113ecbcf84 100644
--- a/addons/website_theme_install/views/views.xml
+++ b/addons/website_theme_install/views/views.xml
@@ -56,6 +56,7 @@
<record id="theme_install_todo_action" model="ir.actions.server">
<field name="name">Config: Choose Your Theme</field>
<field name="model_id" ref="model_ir_module_module"/>
+ <field name="state">code</field>
<field name="code">
model.update_list()
action = {
diff --git a/doc/cla/corporate/levelprime.md b/doc/cla/corporate/levelprime.md
new file mode 100644
index 00000000000..bca6d6aa7d9
--- /dev/null
+++ b/doc/cla/corporate/levelprime.md
@@ -0,0 +1,17 @@
+Italy, 2017-04-18
+
+Level Prime Srl agrees to the terms of the Odoo Corporate Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Roberto Fichera roberto.fichera@levelprime.com https://github.com/robyf70
+
+
+List of contributors:
+
+Roberto Fichera roberto.fichera@levelprime.com https://github.com/robyf70
+Roberto Fichera robyf@tekno-soft.it https://github.com/robyf70
diff --git a/odoo/addons/base/ir/ir_mail_server.py b/odoo/addons/base/ir/ir_mail_server.py
index 2cd8ce7e99e..22f78da6c18 100644
--- a/odoo/addons/base/ir/ir_mail_server.py
+++ b/odoo/addons/base/ir/ir_mail_server.py
@@ -355,9 +355,13 @@ class IrMailServer(models.Model):
msg.attach(email_text_part)
if attachments:
- for (fname, fcontent) in attachments:
+ for (fname, fcontent, mime) in attachments:
filename_rfc2047 = encode_header_param(fname)
- part = MIMEBase('application', "octet-stream")
+ if mime and '/' in mime:
+ maintype, subtype = mime.split('/', 1)
+ part = MIMEBase(maintype, subtype)
+ else:
+ part = MIMEBase('application', "octet-stream")
# The default RFC2231 encoding of Message.add_header() works in Thunderbird but not GMail
# so we fix it by using RFC2047 encoding for the filename instead.
diff --git a/odoo/addons/base/module/module_data.xml b/odoo/addons/base/module/module_data.xml
index 7a6faf7ed13..f3523aaaad0 100644
--- a/odoo/addons/base/module/module_data.xml
+++ b/odoo/addons/base/module/module_data.xml
@@ -209,7 +209,7 @@
</record>
<record id="group_partner_manager" model="res.groups">
- <field name="category_id" ref="module_category_extra"/>
+ <field name="category_id" ref="module_category_usability"/>
</record>
</data>
diff --git a/odoo/cli/start.py b/odoo/cli/start.py
index 306722b23da..88df395eab5 100644
--- a/odoo/cli/start.py
+++ b/odoo/cli/start.py
@@ -35,6 +35,9 @@ class Start(Command):
args, unknown = parser.parse_known_args(args=cmdargs)
+ # When in a virtualenv, by default use it's path rather than the cwd
+ if args.path == '.' and os.environ.get('VIRTUAL_ENV'):
+ args.path = os.environ.get('VIRTUAL_ENV')
project_path = os.path.abspath(os.path.expanduser(os.path.expandvars(args.path)))
module_root = get_module_root(project_path)
db_name = None