diff --git a/.gitignore b/.gitignore index 4b38dc02ac7..4c3277c5215 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/README.md b/README.md index c400772c2ba..a33e4c89e48 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Odoo is a suite of web based open source business apps. More info at http://www The easiest way to play with it is the Odoo free trial, email registration is NOT required, use the "skip this step" link on the registration page to skip it. -Getting started with Odoo developement +Getting started with Odoo development -------------------------------------- If you are a developer type the following command at your terminal [1]: diff --git a/addons/auth_oauth/controllers/main.py b/addons/auth_oauth/controllers/main.py index 4fca426ffc0..99f134a5cbd 100644 --- a/addons/auth_oauth/controllers/main.py +++ b/addons/auth_oauth/controllers/main.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 diff --git a/addons/auth_oauth/res_users.py b/addons/auth_oauth/res_users.py index f58473b24a4..8d57a23f702 100644 --- a/addons/auth_oauth/res_users.py +++ b/addons/auth_oauth/res_users.py @@ -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) diff --git a/addons/base_import/static/src/xml/import.xml b/addons/base_import/static/src/xml/import.xml index 953beca8d9f..0295828cb7f 100644 --- a/addons/base_import/static/src/xml/import.xml +++ b/addons/base_import/static/src/xml/import.xml @@ -73,7 +73,8 @@
- + +

Frequently Asked Questions

diff --git a/addons/base_import_module/bin/oe_module_deploy.py b/addons/base_import_module/bin/oe_module_deploy.py deleted file mode 100755 index 2412382cba5..00000000000 --- a/addons/base_import_module/bin/oe_module_deploy.py +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env python -import argparse -import os -import sys -import tempfile -import zipfile - - -try: - import requests -except ImportError: - # no multipart encoding in stdlib and this script is temporary - sys.exit("This script requires the 'requests' module. ( pip install requests )") - -session = requests.session() - -def deploy_module(module_path, url, login, password, db=''): - url = url.rstrip('/') - authenticate(url, login, password, db) - module_file = zip_module(module_path) - try: - return upload_module(url, module_file) - finally: - os.remove(module_file) - -def upload_module(server, module_file): - print("Uploading module file...") - url = server + '/base_import_module/upload' - files = dict(mod_file=open(module_file, 'rb')) - res = session.post(url, files=files) - if res.status_code != 200: - raise Exception("Could not authenticate on server '%s'" % server) - return res.text - -def authenticate(server, login, password, db=''): - print("Authenticating on server '%s' ..." % server) - - # Fixate session with a given db if any - session.get(server + '/web/login', params=dict(db=db)) - - args = dict(login=login, password=password, db=db) - res = session.post(server + '/base_import_module/login', args) - if res.status_code == 404: - raise Exception("The server '%s' does not have the 'base_import_module' installed." % server) - elif res.status_code != 200: - raise Exception(res.text) - -def zip_module(path): - path = os.path.abspath(path) - if not os.path.isdir(path): - raise Exception("Could not find module directory '%s'" % path) - container, module_name = os.path.split(path) - temp = tempfile.mktemp(suffix='.zip') - try: - print("Zipping module directory...") - with zipfile.ZipFile(temp, 'w') as zfile: - for root, dirs, files in os.walk(path): - for file in files: - file_path = os.path.join(root, file) - zfile.write(file_path, file_path.split(container).pop()) - return temp - except Exception: - os.remove(temp) - raise - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Deploy a module on an OpenERP server.') - parser.add_argument('path', help="Path of the module to deploy") - parser.add_argument('--url', dest='url', help='Url of the server (default=http://localhost:8069)', default="http://localhost:8069") - parser.add_argument('--db', dest='db', help='Database to use if server does not use db-filter.') - parser.add_argument('--login', dest='login', default="admin", help='Login (default=admin)') - parser.add_argument('--password', dest='password', default="admin", help='Password (default=admin)') - parser.add_argument('--no-ssl-check', dest='no_ssl_check', action='store_true', help='Do not check ssl cert') - if len(sys.argv) == 1: - sys.exit(parser.print_help()) - - args = parser.parse_args() - - if args.no_ssl_check: - session.verify = False - - try: - result = deploy_module(args.path, args.url, args.login, args.password, args.db) - print(result) - except Exception, e: - sys.exit("ERROR: %s" % e) diff --git a/addons/calendar/calendar.py b/addons/calendar/calendar.py index 46e8b024dd6..b6473a7f1a7 100644 --- a/addons/calendar/calendar.py +++ b/addons/calendar/calendar.py @@ -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): diff --git a/addons/crm/sales_team_view.xml b/addons/crm/sales_team_view.xml index 7e0eb2e8ad6..f1a4334b311 100644 --- a/addons/crm/sales_team_view.xml +++ b/addons/crm/sales_team_view.xml @@ -126,10 +126,10 @@
+ + + + + + + + + + + +
+

A Punchy Headline

+
+

+ +

A Small Subtitle for ${object.name}

+ +

+ +

Choose a vibrant image and write an inspiring paragraph about it. It does not have to be long, but it should reinforce your image.

+
+ +
+ + + + + + + + + + + +
+

Feature One

+ +

Choose a vibrant image and write an inspiring paragraph about it. It does not have to be long, but it should reinforce your image.

+
+

Feature Two

+ +

Choose a vibrant image and write an inspiring paragraph about it. It does not have to be long, but it should reinforce your image.

+
+
]]> + + Marketing diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py index f721ed2f385..d1c3e7facd4 100644 --- a/addons/mass_mailing/models/mass_mailing.py +++ b/addons/mass_mailing/models/mass_mailing.py @@ -480,9 +480,14 @@ class MassMailing(osv.Model): def on_change_model_and_list(self, cr, uid, ids, mailing_model, list_ids, context=None): value = {} if mailing_model == 'mail.mass_mailing.contact': - list_ids = map(lambda item: item if isinstance(item, (int, long)) else [lid for lid in item[2]], list_ids) - if list_ids: - value['mailing_domain'] = "[('list_id', 'in', %s)]" % list_ids + mailing_list_ids = set() + for item in list_ids: + if isinstance(item, (int, long)): + mailing_list_ids.add(item) + elif len(item) == 3: + mailing_list_ids |= set(item[2]) + if mailing_list_ids: + value['mailing_domain'] = "[('list_id', 'in', %s)]" % list(mailing_list_ids) else: value['mailing_domain'] = "[('list_id', '=', False)]" else: @@ -532,8 +537,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 +576,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 diff --git a/addons/mass_mailing/static/src/js/website_mass_mailing.js b/addons/mass_mailing/static/src/js/website_mass_mailing.js index 5776fadfb3f..2b636aec056 100644 --- a/addons/mass_mailing/static/src/js/website_mass_mailing.js +++ b/addons/mass_mailing/static/src/js/website_mass_mailing.js @@ -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 diff --git a/addons/mass_mailing/views/mass_mailing.xml b/addons/mass_mailing/views/mass_mailing.xml index a9cd33f9531..c7b6eb6f9de 100644 --- a/addons/mass_mailing/views/mass_mailing.xml +++ b/addons/mass_mailing/views/mass_mailing.xml @@ -186,11 +186,17 @@
@@ -250,9 +256,11 @@ -
- + diff --git a/addons/stock/procurement.py b/addons/stock/procurement.py index 0a574579808..f53776d4b9a 100644 --- a/addons/stock/procurement.py +++ b/addons/stock/procurement.py @@ -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') diff --git a/addons/stock/stock.py b/addons/stock/stock.py index aae9676a8a3..b65c718be50 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -2282,6 +2282,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: diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js index 2ea29674d8e..1d97522e011 100644 --- a/addons/web_kanban/static/src/js/kanban.js +++ b/addons/web_kanban/static/src/js/kanban.js @@ -348,10 +348,14 @@ instance.web_kanban.KanbanView = instance.web.View.extend({ }, on_groups_started: function() { var self = this; - if (this.group_by) { + if (this.group_by || this.fields_keys.indexOf("sequence") !== -1) { // Kanban cards drag'n'drop - var prev_widget, is_folded, record; - var $columns = this.$el.find('.oe_kanban_column .oe_kanban_column_cards, .oe_kanban_column .oe_kanban_folded_column_cards'); + var prev_widget, is_folded, record, $columns; + if (this.group_by) { + $columns = this.$el.find('.oe_kanban_column .oe_kanban_column_cards, .oe_kanban_column .oe_kanban_folded_column_cards'); + } else { + $columns = this.$el.find('.oe_kanban_column_cards'); + } $columns.sortable({ handle : '.oe_kanban_draghandle', start: function(event, ui) { @@ -779,9 +783,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(); }); }, diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index 56d954e8a35..f6f9c475f57 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -136,65 +136,7 @@