Merge remote-tracking branch 'odoo/master' into master-inline-searchview-ged

This commit is contained in:
Géry Debongnie 2014-06-11 10:10:58 +02:00
commit af58bc5914
46 changed files with 296 additions and 118 deletions

View File

@ -7,7 +7,6 @@
<field name="period_id" ref="account.period_5"/>
<field name="date" eval="time.strftime('%Y')+'-01-01'"/>
<field name="user_id" ref="base.user_demo"/>
<field name="reference_type">none</field>
<field name="name">BNK/2014/001</field>
<field name="balance_end" eval="2040.0"/>
<field name="company_id" ref="base.main_company"/>

View File

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

View File

@ -139,7 +139,8 @@
<group>
<field name="pricelist_id"
class="oe_inline"
attrs="{'required': [('invoice_on_timesheets', '=', True)]}"/>
attrs="{'required': [('invoice_on_timesheets', '=', True)]}"
domain="[('type', '=', 'sale')]"/>
<field name="to_invoice"
class="oe_inline"
widget="selection"

View File

@ -34,6 +34,7 @@ class ImportModule(Controller):
@route('/base_import_module/upload', type='http', auth='user', methods=['POST'])
@webservice
def upload(self, mod_file=None, **kw):
def upload(self, mod_file=None, force='', **kw):
self.check_user()
return request.registry['ir.module.module'].import_zipfile(request.cr, request.uid, mod_file, context=request.context)[0]
force = True if force == '1' else False
return request.registry['ir.module.module'].import_zipfile(request.cr, request.uid, mod_file, force=force, context=request.context)[0]

View File

@ -12,10 +12,12 @@ class base_import_module(osv.TransientModel):
'module_file': fields.binary('Module .ZIP file', required=True),
'state':fields.selection([('init','init'),('done','done')], 'Status', readonly=True),
'import_message': fields.char('Import message'),
'force': fields.boolean('Force init', help="Force init mode even if installed. (will update `noupdate='1'` records)"),
}
_defaults = {
_defaults = {
'state': 'init',
'force': False,
}
def import_module(self, cr, uid, ids, context=None):
@ -24,7 +26,7 @@ class base_import_module(osv.TransientModel):
zip_data = base64.decodestring(data.module_file)
fp = BytesIO()
fp.write(zip_data)
res = module_obj.import_zipfile(cr, uid, fp, context=context)
res = module_obj.import_zipfile(cr, uid, fp, force=data.force, context=context)
self.write(cr, uid, ids, {'state': 'done', 'import_message': res[0]}, context=context)
context = dict(context, module_name=res[1])
# Return wizard otherwise it will close wizard and will not show result message to user.

View File

@ -16,7 +16,7 @@ MAX_FILE_SIZE = 100 * 1024 * 1024 # in megabytes
class view(osv.osv):
_inherit = "ir.module.module"
def import_module(self, cr, uid, module, path, context=None):
def import_module(self, cr, uid, module, path, force=False, context=None):
known_mods = self.browse(cr, uid, self.search(cr, uid, []))
known_mods_names = dict([(m.name, m) for m in known_mods])
@ -30,7 +30,7 @@ class view(osv.osv):
if mod:
self.write(cr, uid, mod.id, values)
mode = 'update'
mode = 'update' if not force else 'init'
else:
assert terp.get('installable', True), "Module not installable"
self.create(cr, uid, dict(name=module, state='uninstalled', **values))
@ -73,7 +73,7 @@ class view(osv.osv):
return True
def import_zipfile(self, cr, uid, module_file, context=None):
def import_zipfile(self, cr, uid, module_file, force=False, context=None):
if not module_file:
raise Exception("No file sent.")
if not zipfile.is_zipfile(module_file):
@ -95,7 +95,7 @@ class view(osv.osv):
try:
# assert mod_name.startswith('theme_')
path = opj(module_dir, mod_name)
self.import_module(cr, uid, mod_name, path, context=context)
self.import_module(cr, uid, mod_name, path, force=force, context=context)
success.append(mod_name)
except Exception, e:
errors[mod_name] = str(e)

View File

@ -12,6 +12,8 @@
<group states="init" col="4">
<label string="Select module package to import (.zip file):" colspan="4"/>
<field name="module_file" colspan="4"/>
<field name="force"/>
</group>
<group states="done" col="4">
<field name="import_message" colspan="4" nolabel="1" readonly="1"/>

View File

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

View File

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

View File

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

View File

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

View File

@ -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'),
}

View File

@ -1,8 +1,8 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2009-2010 Salvatore J. Trimarchi <salvatore@trimarchi.co.cc>
# (http://salvatoreweb.co.cc)
# Copyright (c) 2009-2010 Salvatore Josué Trimarchi Pinto <salvatore@trigluu.com>
# (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',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1511,8 +1511,9 @@
<t t-if="! widget.options.disable_multiple_selection">
<button type="button" class="oe_button oe_selectcreatepopup-search-select oe_highlight" disabled="disabled">Select</button>
</t>
<t t-if="!widget.options.no_create">
<button type="button" class="oe_button oe_selectcreatepopup-search-create">Create</button>
or <a class="oe_selectcreatepopup-search-close oe_bold oe_form_button_cancel" href="javascript:void(0)">Cancel</a>
or </t><a class="oe_selectcreatepopup-search-close oe_bold oe_form_button_cancel" href="javascript:void(0)">Cancel</a>
</t>
<t t-name="AbstractFormPopup.buttons">
<t t-if="! readonly">

View File

@ -1,2 +0,0 @@
* Keep the minimum files for flotr2.js
* Support clicking on a graph area to filter (or switch view?)

View File

@ -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
<record id="view_project_task_graph" model="ir.ui.view">
<field name="name">project.task.graph</field>
<field name="model">project.task</field>
<field name="arch" type="xml">
<graph string="Project Tasks" type="bar">
<field name="project_id" type="row"/>
<field name="planned_hours" type="measure"/>
</graph>
</field>
</record>
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
<record id="action_view_task" model="ir.actions.act_window">
...
<field name="view_mode">kanban,tree,form,calendar,gantt,graph</field>
...
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
<field name="view_id" ref="view_project_task_graph"/>
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
<graph string="Leads Analysis" type="pivot" stacked="True">
<field name="date_deadline:week" type="row"/>
<field name="stage_id" type="col"/>
<field name="planned_revenue" type="measure"/>
</graph>
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
<record id="..." model="ir.ui.view">
<field name="name">crm.opportunity.report.graph</field>
<field name="model">crm.lead.report</field>
<field name="arch" type="xml">
<graph string="Leads Analysis" type="pivot" stacked="True">
<field name="date_deadline" type="row"/>
<field name="stage_id" type="col"/>
<field name="planned_revenue" type="measure"/>
</graph>
</field>
</record>
**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.

View File

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

View File

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

View File

@ -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");
}

View File

@ -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','<t t-esc="website.google_analytics_key"/>');ga('send','pageview');
ga('create',_.str.trim('<t t-esc="website.google_analytics_key"/>'));
ga('send','pageview');
</script>
</t>
</body>

View File

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

View File

@ -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'],

View File

@ -54,7 +54,7 @@
<li class="nav-header"><h3>Filter by Country</h3></li>
<t t-foreach="countries" t-as="country">
<li t-if="country['country_id']" t-att-class="country['active'] and 'active' or ''">
<a t-attf-href="/partners#{ current_grade and '/grade/%s' % slug(current_grade) or ''}#{country['country_id'][0] and '/country/%s' % country['country_id'][0] or '' }#{ '?' + (search_path or '')}">
<a t-attf-href="/partners#{ current_grade and '/grade/%s' % slug(current_grade) or ''}#{country['country_id'][0] and '/country/%s' % country['country_id'][0] or '' }#{ '?' + (search_path or '') + (country['country_id'][0] == 0 and '&amp;country_all=True' or '')}">
<span class="badge pull-right" t-esc="country['country_id_count'] or ''"/>
<t t-esc="country['country_id'][1]"/>
</a>

View File

@ -134,9 +134,7 @@
</h3>
<ul class="list-inline mb0">
<li t-if="track.speaker_ids" class="text-muted fa fa-user">
<t t-foreach="track.speaker_ids" t-as="speaker">
<t t-esc="speaker.name"/>,
</t>
<t t-esc="', '.join([speaker.name for speaker in track.speaker_ids])"></t>
</li>
<li class="text-muted fa fa-calendar"> <span t-field="track.date" t-field-options='{"hide_seconds":"True"}'/></li>
<li class="text-muted fa fa-map-marker" t-if="track.location_id">

View File

@ -5,7 +5,7 @@
'version': '1.0',
'description': """
OpenERP Website Google Map
========================
==========================
""",
'author': 'OpenERP SA',

View File

@ -5,7 +5,7 @@
'version': '1.0',
'description': """
OpenERP Sale Quote Roller
==================
=========================
""",
'author': 'OpenERP SA',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -83,7 +83,6 @@
<field name="email"/>
<field name="user_id" invisible="1"/>
<field name="is_company" invisible="1"/>
<field name="country" invisible="1"/>
<field name="country_id" invisible="1"/>
<field name="parent_id" invisible="1"/>
</tree>
@ -399,9 +398,9 @@
<li t-if="record.parent_id.raw_value and !record.function.raw_value"><field name="parent_id"/></li>
<li t-if="!record.parent_id.raw_value and record.function.raw_value"><field name="function"/></li>
<li t-if="record.parent_id.raw_value and record.function.raw_value"><field name="function"/> at <field name="parent_id"/></li>
<li t-if="record.city.raw_value and !record.country.raw_value"><field name="city"/></li>
<li t-if="!record.city.raw_value and record.country.raw_value"><field name="country"/></li>
<li t-if="record.city.raw_value and record.country.raw_value"><field name="city"/>, <field name="country"/></li>
<li t-if="record.city.raw_value and !record.country_id.raw_value"><field name="city"/></li>
<li t-if="!record.city.raw_value and record.country_id.raw_value"><field name="country_id"/></li>
<li t-if="record.city.raw_value and record.country_id.raw_value"><field name="city"/>, <field name="country_id"/></li>
<li t-if="record.email.raw_value"><a t-attf-href="mailto:#{record.email.raw_value}"><field name="email"/></a></li>
</ul>
</div>

View File

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

View File

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

View File

@ -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 }}/<model("{{ module }}.{{ controller }}"):{{ controller }}>'], type='http', auth='public', website=True)

View File

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

View File

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

View File

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

View File

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