diff --git a/addons/account/demo/account_bank_statement.xml b/addons/account/demo/account_bank_statement.xml index 52667828cc5..693dbc876b5 100644 --- a/addons/account/demo/account_bank_statement.xml +++ b/addons/account/demo/account_bank_statement.xml @@ -7,7 +7,6 @@ - none BNK/2014/001 diff --git a/addons/account_analytic_analysis/account_analytic_analysis.py b/addons/account_analytic_analysis/account_analytic_analysis.py index 6c710dc2c04..519660fb708 100644 --- a/addons/account_analytic_analysis/account_analytic_analysis.py +++ b/addons/account_analytic_analysis/account_analytic_analysis.py @@ -77,7 +77,7 @@ class account_analytic_invoice_line(osv.osv): price = price_unit elif pricelist_id: price = res.price - else: + if price is False: price = res.list_price if not name: name = self.pool.get('product.product').name_get(cr, uid, [res.id], context=local_context)[0][1] diff --git a/addons/account_analytic_analysis/account_analytic_analysis_view.xml b/addons/account_analytic_analysis/account_analytic_analysis_view.xml index df10eeefff6..f245ac59565 100644 --- a/addons/account_analytic_analysis/account_analytic_analysis_view.xml +++ b/addons/account_analytic_analysis/account_analytic_analysis_view.xml @@ -139,7 +139,8 @@ + attrs="{'required': [('invoice_on_timesheets', '=', True)]}" + domain="[('type', '=', 'sale')]"/> diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index a209bba4890..db9de6a9f0c 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -376,7 +376,6 @@ class crm_lead(format_address, osv.osv): def case_mark_lost(self, cr, uid, ids, context=None): """ Mark the case as lost: state=cancel and probability=0 - :deprecated: this method will be removed in OpenERP v8. """ stages_leads = {} for lead in self.browse(cr, uid, ids, context=context): @@ -397,7 +396,6 @@ class crm_lead(format_address, osv.osv): def case_mark_won(self, cr, uid, ids, context=None): """ Mark the case as won: state=done and probability=100 - :deprecated: this method will be removed in OpenERP v8. """ stages_leads = {} for lead in self.browse(cr, uid, ids, context=context): diff --git a/addons/crm_partner_assign/crm_partner_assign.py b/addons/crm_partner_assign/crm_partner_assign.py index 31a042d8685..2a81e238bfb 100644 --- a/addons/crm_partner_assign/crm_partner_assign.py +++ b/addons/crm_partner_assign/crm_partner_assign.py @@ -191,7 +191,7 @@ class crm_lead(osv.osv): ('partner_weight','>', 0), ('partner_latitude','>', latitude - 8), ('partner_latitude','<', latitude + 8), ('partner_longitude','>', longitude - 8), ('partner_longitude','<', longitude + 8), - ('country', '=', lead.country_id.id), + ('country_id', '=', lead.country_id.id), ], context=context) # 5. fifth way: anywhere in same country diff --git a/addons/google_calendar/__openerp__.py b/addons/google_calendar/__openerp__.py index 7e7a34362f6..9f3eb5a91f8 100644 --- a/addons/google_calendar/__openerp__.py +++ b/addons/google_calendar/__openerp__.py @@ -26,7 +26,7 @@ 'category': 'Tools', 'description': """ The module adds the possibility to synchronize Google Calendar with OpenERP -======================================== +=========================================================================== """, 'author': 'OpenERP SA', 'website': 'http://www.openerp.com', diff --git a/addons/google_spreadsheet/__openerp__.py b/addons/google_spreadsheet/__openerp__.py index 08b08475212..1c429084cf5 100644 --- a/addons/google_spreadsheet/__openerp__.py +++ b/addons/google_spreadsheet/__openerp__.py @@ -26,7 +26,7 @@ 'category': 'Tools', 'description': """ The module adds the possibility to display data from OpenERP in Google Spreadsheets in real time. -======================================== +================================================================================================= """, 'author': 'OpenERP SA', 'website': 'http://www.openerp.com', diff --git a/addons/hr_recruitment/res_config.py b/addons/hr_recruitment/res_config.py index 09d818d3ff2..731c0683481 100644 --- a/addons/hr_recruitment/res_config.py +++ b/addons/hr_recruitment/res_config.py @@ -31,11 +31,6 @@ class hr_applicant_settings(osv.TransientModel): 'module_document': fields.boolean('Allow the automatic indexation of resumes', help='Manage your CV\'s and motivation letter related to all applicants.\n' '-This installs the module document_ftp. This will install the knowledge management module in order to allow you to search using specific keywords through the content of all documents (PDF, .DOCx...)'), - 'fetchmail_applicants': fields.boolean('Create applicants from an incoming email account', - fetchmail_model='hr.applicant', fetchmail_name='Incoming HR Applications', - help='Allow applicants to send their job application to an email address (jobs@mycompany.com), ' - 'and create automatically application documents in the system.', - deprecated='Will be removed with OpenERP v8, not applicable anymore. Use aliases instead.'), 'alias_prefix': fields.char('Default Alias Name for Jobs'), 'alias_domain': fields.char('Alias Domain'), } diff --git a/addons/l10n_hn/__openerp__.py b/addons/l10n_hn/__openerp__.py index 0f72b3e446c..cdf6015100b 100644 --- a/addons/l10n_hn/__openerp__.py +++ b/addons/l10n_hn/__openerp__.py @@ -1,8 +1,8 @@ # -*- encoding: utf-8 -*- ############################################################################## # -# Copyright (c) 2009-2010 Salvatore J. Trimarchi -# (http://salvatoreweb.co.cc) +# Copyright (c) 2009-2010 Salvatore Josué Trimarchi Pinto +# (http://trigluu.com) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -29,7 +29,7 @@ # José Rodrigo Fernández Menegazzo, Soluciones Tecnologócias Prisma S.A. # (http://www.solucionesprisma.com) # -# This module works with OpenERP 6.0 +# This module works with OpenERP 6.0 to 8.0 # { @@ -44,7 +44,7 @@ Agrega una nomenclatura contable para Honduras. También incluye impuestos y la moneda Lempira. -- Adds accounting chart for Honduras. It also includes taxes and the Lempira currency.""", 'author': 'Salvatore Josue Trimarchi Pinto', - 'website': 'http://trimarchi.co.cc', + 'website': 'http://trigluu.com', 'depends': ['base', 'account', 'account_chart'], 'data': [ 'account_types.xml', diff --git a/addons/l10n_vn/__openerp__.py b/addons/l10n_vn/__openerp__.py index 19b81654523..59dc0ad7220 100644 --- a/addons/l10n_vn/__openerp__.py +++ b/addons/l10n_vn/__openerp__.py @@ -27,7 +27,7 @@ "category" : "Localization/Account Charts", "description": """ This is the module to manage the accounting chart for Vietnam in OpenERP. -======================================================================== +========================================================================= This module applies to companies based in Vietnamese Accounting Standard (VAS). diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 36d6ecbf630..55a566880b8 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -1318,14 +1318,6 @@ class mail_thread(osv.AbstractModel): # Note specific #------------------------------------------------------ - def log(self, cr, uid, id, message, secondary=False, context=None): - _logger.warning("log() is deprecated. As this module inherit from "\ - "mail.thread, the message will be managed by this "\ - "module instead of by the res.log mechanism. Please "\ - "use mail_thread.message_post() instead of the "\ - "now deprecated res.log.") - self.message_post(cr, uid, [id], message, context=context) - def _message_add_suggested_recipient(self, cr, uid, result, obj, partner=None, email=None, reason='', context=None): """ Called by message_get_suggested_recipients, to add a suggested recipient in the result dictionary. The form is : diff --git a/addons/point_of_sale/wizard/pos_payment.py b/addons/point_of_sale/wizard/pos_payment.py index fc03ff4a281..a5450f01a30 100644 --- a/addons/point_of_sale/wizard/pos_payment.py +++ b/addons/point_of_sale/wizard/pos_payment.py @@ -123,7 +123,7 @@ class pos_make_payment(osv.osv_memory): } _defaults = { 'journal_id' : _default_journal, - 'payment_date': time.strftime('%Y-%m-%d %H:%M:%S'), + 'payment_date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'), 'amount': _default_amount, } diff --git a/addons/project/project.py b/addons/project/project.py index 2249cf07dd5..d98b9b03e94 100644 --- a/addons/project/project.py +++ b/addons/project/project.py @@ -228,9 +228,6 @@ class project(osv.osv): 'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the project without removing it."), 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of Projects."), 'analytic_account_id': fields.many2one('account.analytic.account', 'Contract/Analytic', help="Link this project to an analytic account if you need financial management on projects. It enables you to connect projects with budgets, planning, cost and revenue analysis, timesheets on projects, etc.", ondelete="cascade", required=True), - 'priority': fields.integer('Sequence (deprecated)', - deprecated='Will be removed with OpenERP v8; use sequence field instead', - help="Gives the sequence order when displaying the list of projects"), 'members': fields.many2many('res.users', 'project_user_rel', 'project_id', 'uid', 'Project Members', help="Project's members are users who can have an access to the tasks related to this project.", states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}), 'tasks': fields.one2many('project.task', 'project_id', "Task Activities"), diff --git a/addons/project_timesheet/project_timesheet.py b/addons/project_timesheet/project_timesheet.py index 767cb4893b5..31a396369e2 100644 --- a/addons/project_timesheet/project_timesheet.py +++ b/addons/project_timesheet/project_timesheet.py @@ -198,7 +198,8 @@ class project_work(osv.osv): if amount_unit and 'amount' in amount_unit.get('value',{}): vals_line['amount'] = amount_unit['value']['amount'] - self.pool.get('hr.analytic.timesheet').write(cr, uid, [line_id.id], vals_line, context=context) + if vals_line: + self.pool.get('hr.analytic.timesheet').write(cr, uid, [line_id.id], vals_line, context=context) return super(project_work,self).write(cr, uid, ids, vals, context) diff --git a/addons/report_webkit/webkit_report.py b/addons/report_webkit/webkit_report.py index c442ea32549..53fe353f898 100644 --- a/addons/report_webkit/webkit_report.py +++ b/addons/report_webkit/webkit_report.py @@ -37,6 +37,7 @@ from openerp import report import tempfile import time import logging +from functools import partial from report_helper import WebKitHelper import openerp @@ -119,7 +120,6 @@ class WebKitParser(report_sxw): """ def __init__(self, name, table, rml=False, parser=rml_parse, header=True, store=False, register=True): - self.parser_instance = False self.localcontext = {} report_sxw.__init__(self, name, table, rml, parser, header, store, register=register) @@ -230,16 +230,16 @@ class WebKitParser(report_sxw): _logger.error('cannot remove file %s: %s', f_to_del, exc) return pdf - def translate_call(self, src): + def translate_call(self, parser_instance, src): """Translate String.""" ir_translation = self.pool['ir.translation'] name = self.tmpl and 'addons/' + self.tmpl or None - res = ir_translation._get_source(self.parser_instance.cr, self.parser_instance.uid, - name, 'report', self.parser_instance.localcontext.get('lang', 'en_US'), src) + res = ir_translation._get_source(parser_instance.cr, parser_instance.uid, + name, 'report', parser_instance.localcontext.get('lang', 'en_US'), src) if res == src: # no translation defined, fallback on None (backward compatibility) - res = ir_translation._get_source(self.parser_instance.cr, self.parser_instance.uid, - None, 'report', self.parser_instance.localcontext.get('lang', 'en_US'), src) + res = ir_translation._get_source(parser_instance.cr, parser_instance.uid, + None, 'report', parser_instance.localcontext.get('lang', 'en_US'), src) if not res : return src return res @@ -264,14 +264,14 @@ class WebKitParser(report_sxw): if report_xml.report_type != 'webkit': return super(WebKitParser,self).create_single_pdf(cursor, uid, ids, data, report_xml, context=context) - self.parser_instance = self.parser(cursor, - uid, - self.name2, - context=context) + parser_instance = self.parser(cursor, + uid, + self.name2, + context=context) self.pool = pool objs = self.getObjects(cursor, uid, ids, context) - self.parser_instance.set_context(objs, data, ids, report_xml.report_type) + parser_instance.set_context(objs, data, ids, report_xml.report_type) template = False @@ -299,21 +299,22 @@ class WebKitParser(report_sxw): if not css : css = '' + translate_call = partial(self.translate_call, parser_instance) body_mako_tpl = mako_template(template) helper = WebKitHelper(cursor, uid, report_xml.id, context) - self.parser_instance.localcontext['helper'] = helper - self.parser_instance.localcontext['css'] = css - self.parser_instance.localcontext['_'] = self.translate_call + parser_instance.localcontext['helper'] = helper + parser_instance.localcontext['css'] = css + parser_instance.localcontext['_'] = translate_call # apply extender functions additional = {} if xml_id in _extender_functions: for fct in _extender_functions[xml_id]: - fct(pool, cr, uid, self.parser_instance.localcontext, context) + fct(pool, cr, uid, parser_instance.localcontext, context) if report_xml.precise_mode: - ctx = dict(self.parser_instance.localcontext) - for obj in self.parser_instance.localcontext['objects']: + ctx = dict(parser_instance.localcontext) + for obj in parser_instance.localcontext['objects']: ctx['objects'] = [obj] try : html = body_mako_tpl.render(dict(ctx)) @@ -324,7 +325,7 @@ class WebKitParser(report_sxw): raise except_osv(_('Webkit render!'), msg) else: try : - html = body_mako_tpl.render(dict(self.parser_instance.localcontext)) + html = body_mako_tpl.render(dict(parser_instance.localcontext)) htmls.append(html) except Exception, e: msg = u"%s" % e @@ -332,22 +333,21 @@ class WebKitParser(report_sxw): raise except_osv(_('Webkit render!'), msg) head_mako_tpl = mako_template(header) try : - head = head_mako_tpl.render(dict(self.parser_instance.localcontext, _debug=False)) + head = head_mako_tpl.render(dict(parser_instance.localcontext, _debug=False)) except Exception, e: raise except_osv(_('Webkit render!'), u"%s" % e) foot = False if footer : foot_mako_tpl = mako_template(footer) try : - foot = foot_mako_tpl.render(dict({}, - **self.parser_instance.localcontext)) + foot = foot_mako_tpl.render(dict(parser_instance.localcontext)) except Exception, e: msg = u"%s" % e _logger.error(msg) raise except_osv(_('Webkit render!'), msg) if report_xml.webkit_debug : try : - deb = head_mako_tpl.render(dict(self.parser_instance.localcontext, _debug=tools.ustr("\n".join(htmls)))) + deb = head_mako_tpl.render(dict(parser_instance.localcontext, _debug=tools.ustr("\n".join(htmls)))) except Exception, e: msg = u"%s" % e _logger.error(msg) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 1e9a27f0b4f..fd9598eebd5 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -2595,7 +2595,7 @@ instance.web.form.FieldCharDomain = instance.web.form.AbstractField.extend(insta }; this.$('.select_records').on('click', self.on_click); }, - on_click: function(ev) { + on_click: function(event) { event.preventDefault(); var self = this; var model = this.options.model || this.field_manager.get_field_value(this.options.model_field); @@ -4858,7 +4858,8 @@ instance.web.form.Many2ManyListView = instance.web.ListView.extend(/** @lends in pop.select_element( this.model, { - title: _t("Add: ") + this.m2m_field.string + title: _t("Add: ") + this.m2m_field.string, + no_create: this.m2m_field.options.no_create, }, new instance.web.CompoundDomain(this.m2m_field.build_domain(), ["!", ["id", "in", this.m2m_field.dataset.ids]]), this.m2m_field.build_context() diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 7055732a8da..3a39a7d1457 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -1511,8 +1511,9 @@ + - or Cancel + or Cancel diff --git a/addons/web_graph/doc/TODO.txt b/addons/web_graph/doc/TODO.txt deleted file mode 100644 index b7969527a1f..00000000000 --- a/addons/web_graph/doc/TODO.txt +++ /dev/null @@ -1,2 +0,0 @@ -* Keep the minimum files for flotr2.js -* Support clicking on a graph area to filter (or switch view?) diff --git a/addons/web_graph/doc/index.rst b/addons/web_graph/doc/index.rst new file mode 100644 index 00000000000..be5c0a9f88c --- /dev/null +++ b/addons/web_graph/doc/index.rst @@ -0,0 +1,178 @@ + +======================================== +OpenERP BI View (a.k.a. Graph View) +======================================== + +Outline +''''''' + +* BI in Odoo +* Adding a Graph view +* Graph view parameters +* Fields: measures and dimensions +* Graph view architecture + +BI in Odoo +'''''''''' + +The graph view gives a way to look at your data and to visualize it in various ways. Currently, it supports four main modes: + +* **pivot**: it is probably the most important mode. It is a multidimensional table that you can configure to display any possible cube for your data. +* **bar**: displays a bar chart +* **pie**: displays a pie chart +* **line**: displays a line chart + +The basic idea is that you can work in pivot table mode to select the desired dimensions, use the search bar to select appropriate filters, and switch to various chart modes when your data is ready. + +**Note**: the technical name for the graph view is *web_graph*. + +Fields: measures and dimensions +''''''''''''''''''''''''''''''' + +To do its work, the graph view understands two kind of fields: + +* **measure**: a measure is a field that can be aggregated (right now, the only aggregation method is the sum). Every field of type *integer* or *float* (except the *id* field) can be used as a measure. +* **dimension**: A dimension is a field that can be grouped. That means pretty much every non numeric fields. + + +When loading, the graph view reads the list of fields from the model. The measures are then obtained from that list. The dimensions are right now obtained from the *Group By* filters category defined in the search bar, but in a later release, the dimensions will be every groupable fields in the model. + + +**Note**: it is important to note that the fields value are obtained with the *read_group* method in the ORM. It means that it directly reads from the database, and can not evaluate non-stored functional fields. If you want to use functional fields as measure or dimension, make sure to store them in the database (with the *stored=true* attribute) + + +Adding a Graph View +''''''''''''''''''' + +In general, the process to add a graph view is: + +1. register a graph view in a xml file. For example, this code registers a graph view for the model *project.task*. The *graph* tag is where the graph view default parameters are defined. These parameters will be explained in the next section. + +.. code-block:: xml + + + project.task.graph + project.task + + + + + + + + + +2. Add the graph view to an action (of course, if there is no action yet, an action has to be created as well). In this example, the *graph* value in the view_mode field is where the web client will see that a graph view as to be created when the action *action_view_task* is triggered. + +.. code-block:: xml + + + ... + kanban,tree,form,calendar,gantt,graph + ... + +3. If necessary, force the correct view with its id. The way the client operates is quite simple: it will pick the lowest priority available view for a given model. If this behaviour is not what you need, you can force it with the *view_id* field: + +.. code-block:: xml + + + + +Graph view parameters +'''''''''''''''''''''' + +In *graph* tag: +--------------- + +* string: title of the graph +* stacked: if bar chart is stacked/not stacked (default=false) +* type: mode (pivot, bar, pie, line) (default=bar). This parameter determines the mode in which the graph view will be when it loads. + +The *type* attribute: +--------------------- + +The *graph* tag can contain a number of *field* subtags. These fields should have a name attribute (corresponding to the name of a field in the corresponding model). The other main attribute is *type*. Here are its possible values: + +* row : the field will be grouped by rows (dimension) +* col : the field will be grouped by cols (dimension) +* measure : the field will be aggregated +* if no type, measure by default + +The order is important: for example if two fields are grouped by row, then the first one that appears in the xml description will be the first one used to group, and the second will be used as a way to define sub groups. + +Date/datetime +------------- + +Dates and datetimes are always a little tricky. There is a special syntax for grouping them by intervals. + +* field_date:day, +* field_date:week, +* field_date:month (default) +* field_date:quarter, +* field_date:year + +For example, + +.. code-block:: xml + + + + + + + + +Example: +-------- +Here is an example of a graph view defined for the model *crm.lead.report*. It will open in pivot table mode. If it is switched to bar chart mode, the bars will be stacked. The data will be grouped according to the date_deadline field in rows, and the columns will be the various stages of an opportunity. Also, the *planned_revenue* field will be used as a measure. + +.. code-block:: xml + + + crm.opportunity.report.graph + crm.lead.report + + + + + + + + + +**Note**: the old graph view syntax still works (for example, operator="+"), but it is a good idea to use the new syntax whenever possible. + + + +Graph view architecture +''''''''''''''''''''''' + +Overview +-------- + +The general design of the graph view is quite simple. It is basically a javascript addon, so it lives in the client. When it needs data from the model, it makes an async request to the server (only read_group calls), and displays the result as soon as it gets it. + +So, it means that the aggregation is done by the database (hence the constraint that functional fields need to be stored). + +Also, note that it is basically *lazy*: it only request the data that it needs. For example, if you drill down in a pivot table, it will only request the data corresponding to the subgroups. + + +Graph view +---------- + +The graph view (addon *web_graph*) is actually made out of three parts: + +* **pivot table**: this is the part that keeps the data in memory and takes care of calling the ORM with ajax calls. +* **graph widget**: this is a normal Odoo widget that takes care of displaying the data (the graph view is actually managed by a large widget) and interacting with the user +* **graph view**: its task is to interact with the web client. So, basically, it only needs to instantiate a graph widget, and communicating both ways with the search view and the widget. + +Because of that design, it is possible (for example, in a client action) to display only a widget without the graph view. The widget has the full power of a normal graph view. + +Cross-model BI +-------------- + +Due to its design, it is not possible to display a graph view for more than one model at a time. A graph view is tied to one and only one model. + +However, a workaround is to create a new model that contains all the necessary data. That model can fetch its data from a postgres view (so, it can view, but not edit the data). It means that you can define any desired field from any table. However, be careful because doing so bypass the security checks from the ORM. + +Right now, most reporting views work that way, by defining custom views. \ No newline at end of file diff --git a/addons/web_kanban/static/src/css/kanban.css b/addons/web_kanban/static/src/css/kanban.css index 5ef666a970e..50a9e54d7be 100644 --- a/addons/web_kanban/static/src/css/kanban.css +++ b/addons/web_kanban/static/src/css/kanban.css @@ -93,6 +93,9 @@ .openerp .oe_kanban_view .oe_kanban_groups { height: inherit; } +.openerp .oe_kanban_view .oe_kanban_groups_records { + height: 100%; +} .openerp .oe_kanban_view.oe_kanban_grouped_by_m2o .oe_kanban_group_title { cursor: move; } @@ -160,7 +163,7 @@ padding: 0px; background: white; } -.openerp .oe_kanban_view .oe_kanban_column, .openerp .oe_kanban_view .oe_kanban_column_cards { +.openerp .oe_kanban_view.oe_kanban_grouped .oe_kanban_column, .openerp .oe_kanban_view.oe_kanban_grouped .oe_kanban_column_cards { height: 100%; } .openerp .oe_kanban_view .oe_kanban_aggregates { diff --git a/addons/web_kanban/static/src/css/kanban.sass b/addons/web_kanban/static/src/css/kanban.sass index 9fe3737a410..d5f2bfcdbd8 100644 --- a/addons/web_kanban/static/src/css/kanban.sass +++ b/addons/web_kanban/static/src/css/kanban.sass @@ -119,6 +119,8 @@ // KanbanGroups {{{ .oe_kanban_groups height: inherit + .oe_kanban_groups_records + height: 100% &.oe_kanban_grouped_by_m2o .oe_kanban_group_title cursor: move .oe_kanban_header @@ -178,9 +180,9 @@ .oe_kanban_column padding: 0px background: #ffffff - - .oe_kanban_column, .oe_kanban_column_cards - height: 100% + &.oe_kanban_grouped + .oe_kanban_column, .oe_kanban_column_cards + height: 100% .oe_kanban_aggregates padding: 0 margin: 0px diff --git a/addons/website/static/src/js/website.snippets.editor.js b/addons/website/static/src/js/website.snippets.editor.js index 806b2ab4e0c..7e94895c515 100644 --- a/addons/website/static/src/js/website.snippets.editor.js +++ b/addons/website/static/src/js/website.snippets.editor.js @@ -977,6 +977,8 @@ clean_for_save: function () { this._super(); $(".carousel").find(".item").removeClass("next prev left right active"); + this.$indicators.find('li').removeClass('active'); + this.$indicators.find('li:first').addClass('active'); if(!this.$target.find(".item.active").length) { this.$target.find(".item:first").addClass("active"); } diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index f6f9c475f57..e5c1bfc0f86 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -156,7 +156,8 @@ e=o.createElement(i);r=o.getElementsByTagName(i)[0]; e.src='//www.google-analytics.com/analytics.js'; r.parentNode.insertBefore(e,r)}(window,document,'script','ga')); - ga('create','');ga('send','pageview'); + ga('create',_.str.trim('')); + ga('send','pageview'); diff --git a/addons/website_blog/models/website_blog.py b/addons/website_blog/models/website_blog.py index 37678e94aba..47c6df94a6b 100644 --- a/addons/website_blog/models/website_blog.py +++ b/addons/website_blog/models/website_blog.py @@ -74,7 +74,6 @@ class BlogPost(osv.Model): 'history_ids': fields.one2many( 'blog.post.history', 'post_id', 'History', help='Last post modifications', - deprecated='This field will be removed for OpenERP v9.' ), # creation / update stuff 'create_date': fields.datetime( diff --git a/addons/website_crm/controllers/main.py b/addons/website_crm/controllers/main.py index fb4535bf3a6..2cd6154e9e8 100644 --- a/addons/website_crm/controllers/main.py +++ b/addons/website_crm/controllers/main.py @@ -34,7 +34,6 @@ class contactus(http.Controller): 'contact_name': contact_name, 'email_from': email_from, 'name': name or contact_name, - 'user_id': False, } # fields validation @@ -51,6 +50,7 @@ class contactus(http.Controller): except ValueError: pass + post['user_id'] = False environ = request.httprequest.headers.environ post['description'] = "%s\n-----------------------------\nIP: %s\nUSER_AGENT: %s\nACCEPT_LANGUAGE: %s\nREFERER: %s" % ( post['description'], diff --git a/addons/website_crm_partner_assign/views/website_crm_partner_assign.xml b/addons/website_crm_partner_assign/views/website_crm_partner_assign.xml index 8db6970505c..3f0e52879e8 100644 --- a/addons/website_crm_partner_assign/views/website_crm_partner_assign.xml +++ b/addons/website_crm_partner_assign/views/website_crm_partner_assign.xml @@ -54,7 +54,7 @@
  • - + diff --git a/addons/website_event_track/views/website_event.xml b/addons/website_event_track/views/website_event.xml index 9aece0f5c11..05da470c041 100644 --- a/addons/website_event_track/views/website_event.xml +++ b/addons/website_event_track/views/website_event.xml @@ -134,9 +134,7 @@
    • - - , - +
    • diff --git a/addons/website_google_map/__openerp__.py b/addons/website_google_map/__openerp__.py index f54009c9ab1..5f3d9feefd1 100644 --- a/addons/website_google_map/__openerp__.py +++ b/addons/website_google_map/__openerp__.py @@ -5,7 +5,7 @@ 'version': '1.0', 'description': """ OpenERP Website Google Map -======================== +========================== """, 'author': 'OpenERP SA', diff --git a/addons/website_quote/__openerp__.py b/addons/website_quote/__openerp__.py index 3991f6ac17e..975451ffb60 100644 --- a/addons/website_quote/__openerp__.py +++ b/addons/website_quote/__openerp__.py @@ -5,7 +5,7 @@ 'version': '1.0', 'description': """ OpenERP Sale Quote Roller -================== +========================= """, 'author': 'OpenERP SA', diff --git a/openerp/addons/base/ir/ir_cron.py b/openerp/addons/base/ir/ir_cron.py index e5a5e5f024b..e0f6e38b5ba 100644 --- a/openerp/addons/base/ir/ir_cron.py +++ b/openerp/addons/base/ir/ir_cron.py @@ -238,7 +238,7 @@ class ir_cron(osv.osv): locked_job = lock_cr.fetchone() if not locked_job: - # job was already executed by another parallel process/thread, skipping it. + _logger.debug("Job `%s` already executed by another process/thread. skipping it", job['name']) continue # Got the lock on the job row, run its code _logger.debug('Starting job `%s`.', job['name']) diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py index 442dc0ce741..3101f52f2e0 100644 --- a/openerp/addons/base/ir/ir_model.py +++ b/openerp/addons/base/ir/ir_model.py @@ -1141,13 +1141,20 @@ class ir_model_data(osv.osv): # Remove non-model records first, then model fields, and finish with models unlink_if_refcount((model, res_id) for model, res_id in to_unlink - if model not in ('ir.model','ir.model.fields')) + if model not in ('ir.model','ir.model.fields','ir.model.constraint')) + unlink_if_refcount((model, res_id) for model, res_id in to_unlink + if model == 'ir.model.constraint') + + ir_module_module = self.pool['ir.module.module'] + ir_model_constraint = self.pool['ir.model.constraint'] + modules_to_remove_ids = ir_module_module.search(cr, uid, [('name', 'in', modules_to_remove)], context=context) + constraint_ids = ir_model_constraint.search(cr, uid, [('module', 'in', modules_to_remove_ids)], context=context) + ir_model_constraint._module_data_uninstall(cr, uid, constraint_ids, context) + unlink_if_refcount((model, res_id) for model, res_id in to_unlink if model == 'ir.model.fields') ir_model_relation = self.pool['ir.model.relation'] - ir_module_module = self.pool['ir.module.module'] - modules_to_remove_ids = ir_module_module.search(cr, uid, [('name', 'in', modules_to_remove)]) relation_ids = ir_model_relation.search(cr, uid, [('module', 'in', modules_to_remove_ids)]) ir_model_relation._module_data_uninstall(cr, uid, relation_ids, context) diff --git a/openerp/addons/base/ir/ir_qweb.py b/openerp/addons/base/ir/ir_qweb.py index 11c2432a874..6e434c2ab1d 100644 --- a/openerp/addons/base/ir/ir_qweb.py +++ b/openerp/addons/base/ir/ir_qweb.py @@ -334,10 +334,10 @@ class QWeb(orm.AbstractModel): att, val = attribute_name[7:], self.eval_format(attribute_value, qwebcontext) elif attribute_name.startswith("t-att-"): att, val = attribute_name[6:], self.eval(attribute_value, qwebcontext) - if isinstance(val, unicode): - val = val.encode("utf8") else: att, val = self.eval_object(attribute_value, qwebcontext) + if val and not isinstance(val, str): + val = unicode(val).encode("utf8") return att, val # Tags diff --git a/openerp/addons/base/module/module.py b/openerp/addons/base/module/module.py index adf21580965..eb154d4a05f 100644 --- a/openerp/addons/base/module/module.py +++ b/openerp/addons/base/module/module.py @@ -445,11 +445,7 @@ class module(osv.osv): including the deletion of all database structures created by the module: tables, columns, constraints, etc.""" ir_model_data = self.pool.get('ir.model.data') - ir_model_constraint = self.pool.get('ir.model.constraint') modules_to_remove = [m.name for m in self.browse(cr, uid, ids, context)] - modules_to_remove_ids = [m.id for m in self.browse(cr, uid, ids, context)] - constraint_ids = ir_model_constraint.search(cr, uid, [('module', 'in', modules_to_remove_ids)]) - ir_model_constraint._module_data_uninstall(cr, uid, constraint_ids, context) ir_model_data._module_data_uninstall(cr, uid, modules_to_remove, context) self.write(cr, uid, ids, {'state': 'uninstalled'}) return True diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index c7af837aff1..b2792fb254b 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -266,8 +266,6 @@ class res_partner(osv.osv, format_address): 'city': fields.char('City', size=128), 'state_id': fields.many2one("res.country.state", 'State', ondelete='restrict'), 'country_id': fields.many2one('res.country', 'Country', ondelete='restrict'), - 'country': fields.related('country_id', type='many2one', relation='res.country', string='Country', - deprecated="This field will be removed as of OpenERP 7.1, use country_id instead"), 'email': fields.char('Email', size=240), 'phone': fields.char('Phone', size=64), 'fax': fields.char('Fax', size=64), diff --git a/openerp/addons/base/res/res_partner_view.xml b/openerp/addons/base/res/res_partner_view.xml index 3be3b94c15d..64fba53723f 100644 --- a/openerp/addons/base/res/res_partner_view.xml +++ b/openerp/addons/base/res/res_partner_view.xml @@ -83,7 +83,6 @@ - @@ -399,9 +398,9 @@
    • at
    • -
    • -
    • -
    • ,
    • +
    • +
    • +
    • ,
    diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index b0731791ae0..8505be0e59e 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -182,9 +182,6 @@ class res_users(osv.osv): 'company_id': fields.many2one('res.company', 'Company', required=True, help='The company this user is currently working for.', context={'user_preference': True}), 'company_ids':fields.many2many('res.company','res_company_users_rel','user_id','cid','Companies'), - # backward compatibility fields - 'user_email': fields.related('email', type='char', - deprecated='Use the email field instead of user_email. This field will be removed with OpenERP 7.1.'), } def on_change_login(self, cr, uid, ids, login, context=None): diff --git a/openerp/cli/deploy.py b/openerp/cli/deploy.py index 36ff0717377..7c5e9536c0c 100644 --- a/openerp/cli/deploy.py +++ b/openerp/cli/deploy.py @@ -15,20 +15,21 @@ class Deploy(Command): super(Deploy, self).__init__() self.session = requests.session() - def deploy_module(self, module_path, url, login, password, db=''): + def deploy_module(self, module_path, url, login, password, db='', force=False): url = url.rstrip('/') self.authenticate(url, login, password, db) module_file = self.zip_module(module_path) try: - return self.upload_module(url, module_file) + return self.upload_module(url, module_file, force=force) finally: os.remove(module_file) - def upload_module(self, server, module_file): + def upload_module(self, server, module_file, force=False): print("Uploading module file...") url = server + '/base_import_module/upload' files = dict(mod_file=open(module_file, 'rb')) - res = self.session.post(url, files=files) + force = '1' if force else '' + res = self.session.post(url, files=files, data=dict(force=force)) if res.status_code != 200: raise Exception("Could not authenticate on server '%s'" % server) return res.text @@ -75,6 +76,7 @@ class Deploy(Command): 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('--verify-ssl', action='store_true', help='Verify SSL certificate') + parser.add_argument('--force', action='store_true', help='Force init even if module is already installed. (will update `noupdate="1"` records)') if not cmdargs: sys.exit(parser.print_help()) @@ -86,7 +88,7 @@ class Deploy(Command): try: if not args.url.startswith(('http://', 'https://')): args.url = 'https://%s' % args.url - result = self.deploy_module(args.path, args.url, args.login, args.password, args.db) + result = self.deploy_module(args.path, args.url, args.login, args.password, args.db, force=args.force) print(result) except Exception, e: sys.exit("ERROR: %s" % e) diff --git a/openerp/cli/scaffold/controllers.jinja2 b/openerp/cli/scaffold/controllers.jinja2 index 0cdb05cc74b..cbe0fdaed7a 100644 --- a/openerp/cli/scaffold/controllers.jinja2 +++ b/openerp/cli/scaffold/controllers.jinja2 @@ -3,7 +3,7 @@ from openerp import http class {{ controller }}(http.Controller): @http.route('/{{ module }}/{{ controller }}', auth='public', website=True) - def index(self): + def index(self, **kw): return "Hello, world!" {% if has_model %} @http.route('/{{ module }}/{{ controller }}/'], type='http', auth='public', website=True) diff --git a/openerp/cli/start.py b/openerp/cli/start.py index b3bd47ede63..d23bca8b8d5 100644 --- a/openerp/cli/start.py +++ b/openerp/cli/start.py @@ -48,15 +48,16 @@ class Start(Command): if not args.db_name: args.db_name = db_name or project_path.split(os.path.sep)[-1] - # TODO: forbid some database names ? eg template1, ... - try: - _create_empty_database(args.db_name) - except DatabaseExists, e: - pass - except Exception, e: - die("Could not create database `%s`. (%s)" % (args.db_name, e)) cmdargs.extend(('-d', args.db_name)) + # TODO: forbid some database names ? eg template1, ... + try: + _create_empty_database(args.db_name) + except DatabaseExists, e: + pass + except Exception, e: + die("Could not create database `%s`. (%s)" % (args.db_name, e)) + if '--db-filter' not in cmdargs: cmdargs.append('--db-filter=^%s$' % args.db_name) diff --git a/openerp/netsvc.py b/openerp/netsvc.py index 59a155ff2d0..c84228ea6e5 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -75,7 +75,6 @@ class PostgreSQLHandler(logging.Handler): def emit(self, record): ct = threading.current_thread() ct_db = getattr(ct, 'dbname', None) - ct_uid = getattr(ct, 'uid', None) dbname = tools.config['log_db'] or ct_db if not dbname: return @@ -88,10 +87,10 @@ class PostgreSQLHandler(logging.Handler): msg = "%s\n%s" % (msg, traceback) # we do not use record.levelname because it may have been changed by ColoredFormatter. levelname = logging.getLevelName(record.levelno) - val = (ct_uid, ct_uid, 'server', ct_db, record.name, levelname, msg, record.pathname, record.lineno, record.funcName) + val = ('server', ct_db, record.name, levelname, msg, record.pathname, record.lineno, record.funcName) cr.execute(""" - INSERT INTO ir_logging(create_date, write_date, create_uid, write_uid, type, dbname, name, level, message, path, line, func) - VALUES (NOW() at time zone 'UTC', NOW() at time zone 'UTC', %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) + INSERT INTO ir_logging(create_date, type, dbname, name, level, message, path, line, func) + VALUES (NOW() at time zone 'UTC', %s, %s, %s, %s, %s, %s, %s, %s) """, val) BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, _NOTHING, DEFAULT = range(10) diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index 439463b962a..a2bb213cbf6 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -1261,8 +1261,16 @@ class function(_column): return new_values def get(self, cr, obj, ids, name, uid=False, context=None, values=None): - result = self._fnct(obj, cr, uid, ids, name, self._arg, context) - if self._multi: + multi = self._multi + # if we already have a value, don't recompute it. + # This happen if case of stored many2one fields + if values and not multi and name in values[0]: + result = {v['id']: v[name] for v in values} + elif values and multi and all(n in values[0] for n in name): + result = {v['id']: dict({n: v[n]} for n in name) for v in values} + else: + result = self._fnct(obj, cr, uid, ids, name, self._arg, context) + if multi: swap = {} for rid, values in result.iteritems(): for f, v in values.iteritems(): diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index e85a35b790a..73ad3796418 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -2806,7 +2806,7 @@ class BaseModel(object): ('numeric', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]), ('float8', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]), ] - if f_pg_type == 'varchar' and f._type == 'char' and ((f.size is None and f_pg_size) or f_pg_size < f.size): + if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size and (f.size is None or f_pg_size < f.size): try: with cr.savepoint(): cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" TYPE %s' % (self._table, k, pg_varchar(f.size)))