[MERGE] forward port of branch 8.0 up to 4af53de
ignore .po and .pot changes
This commit is contained in:
@@ -148,8 +148,8 @@ class account_invoice_report(osv.osv):
|
||||
ai.partner_bank_id,
|
||||
SUM(CASE
|
||||
WHEN ai.type::text = ANY (ARRAY['out_refund'::character varying::text, 'in_invoice'::character varying::text])
|
||||
THEN (- ail.quantity) / u.factor
|
||||
ELSE ail.quantity / u.factor
|
||||
THEN (- ail.quantity) / u.factor * u2.factor
|
||||
ELSE ail.quantity / u.factor * u2.factor
|
||||
END) AS product_qty,
|
||||
SUM(CASE
|
||||
WHEN ai.type::text = ANY (ARRAY['out_refund'::character varying::text, 'in_invoice'::character varying::text])
|
||||
@@ -197,6 +197,7 @@ class account_invoice_report(osv.osv):
|
||||
LEFT JOIN product_product pr ON pr.id = ail.product_id
|
||||
left JOIN product_template pt ON pt.id = pr.product_tmpl_id
|
||||
LEFT JOIN product_uom u ON u.id = ail.uos_id
|
||||
LEFT JOIN product_uom u2 ON u.id = pt.uom_id
|
||||
"""
|
||||
return from_str
|
||||
|
||||
|
||||
@@ -91,18 +91,18 @@ class tax_report(report_sxw.rml_parse, common_report_header):
|
||||
i = 0
|
||||
top_result = []
|
||||
while i < len(res):
|
||||
res_dict = { 'code': res[i][1].code,
|
||||
'name': res[i][1].name,
|
||||
res_dict = { 'code': res[i][1]['code'],
|
||||
'name': res[i][1]['name'],
|
||||
'debit': 0,
|
||||
'credit': 0,
|
||||
'tax_amount': res[i][1].sum_period,
|
||||
'tax_amount': res[i][1]['sum_period'],
|
||||
'type': 1,
|
||||
'level': res[i][0],
|
||||
'pos': 0
|
||||
}
|
||||
|
||||
top_result.append(res_dict)
|
||||
res_general = self._get_general(res[i][1].id, period_list, company_id, based_on, context=context)
|
||||
res_general = self._get_general(res[i][1]['id'], period_list, company_id, based_on, context=context)
|
||||
ind_general = 0
|
||||
while ind_general < len(res_general):
|
||||
res_general[ind_general]['type'] = 2
|
||||
@@ -171,7 +171,7 @@ class tax_report(report_sxw.rml_parse, common_report_header):
|
||||
|
||||
def _get_codes(self, based_on, company_id, parent=False, level=0, period_list=None, context=None):
|
||||
obj_tc = self.pool.get('account.tax.code')
|
||||
ids = obj_tc.search(self.cr, self.uid, [('parent_id','=',parent),('company_id','=',company_id)], order='sequence', context=context)
|
||||
ids = obj_tc.search(self.cr, self.uid, [('parent_id','=',parent),('company_id','=',company_id)], context=context)
|
||||
|
||||
res = []
|
||||
for code in obj_tc.browse(self.cr, self.uid, ids, {'based_on': based_on}):
|
||||
@@ -192,13 +192,20 @@ class tax_report(report_sxw.rml_parse, common_report_header):
|
||||
obj_tc = self.pool.get('account.tax.code')
|
||||
for account in account_list:
|
||||
ids = obj_tc.search(self.cr, self.uid, [('id','=', account[1].id)], context=context)
|
||||
sum_tax_add = 0
|
||||
code = {}
|
||||
for period_ind in period_list:
|
||||
context2 = dict(context, period_id=period_ind, based_on=based_on)
|
||||
for code in obj_tc.browse(self.cr, self.uid, ids, context=context2):
|
||||
sum_tax_add = sum_tax_add + code.sum_period
|
||||
record = obj_tc.browse(self.cr, self.uid, ids, context=context2)[0]
|
||||
if not code:
|
||||
code = {
|
||||
'id': record.id,
|
||||
'name': record.name,
|
||||
'code': record.code,
|
||||
'sequence': record.sequence,
|
||||
'sum_period': 0,
|
||||
}
|
||||
code['sum_period'] += record.sum_period
|
||||
|
||||
code.sum_period = sum_tax_add
|
||||
res.append((account[0], code))
|
||||
return res
|
||||
|
||||
|
||||
@@ -92,6 +92,10 @@ class res_partner(osv.osv):
|
||||
getattr(vatnumber, check_func_name, None)
|
||||
if not check_func:
|
||||
# No VAT validation available, default to check that the country code exists
|
||||
if country_code.upper() == 'EU':
|
||||
# Foreign companies that trade with non-enterprises in the EU
|
||||
# may have a VATIN starting with "EU" instead of a country code.
|
||||
return True
|
||||
res_country = self.pool.get('res.country')
|
||||
return bool(res_country.search(cr, uid, [('code', '=ilike', country_code)], context=context))
|
||||
return check_func(vat_number)
|
||||
|
||||
@@ -30,7 +30,11 @@ class res_partner(osv.osv):
|
||||
# the user may not have access rights for opportunities or meetings
|
||||
try:
|
||||
for partner in self.browse(cr, uid, ids, context):
|
||||
opp_ids = self.pool['crm.lead'].search(cr, uid, [('partner_id', '=', partner.id), ('type', '=', 'opportunity'), ('probability', '<', '100')], context=context)
|
||||
if partner.is_company:
|
||||
operator = 'child_of'
|
||||
else:
|
||||
operator = '='
|
||||
opp_ids = self.pool['crm.lead'].search(cr, uid, [('partner_id', operator, partner.id), ('type', '=', 'opportunity'), ('probability', '<', '100')], context=context)
|
||||
res[partner.id] = {
|
||||
'opportunity_count': len(opp_ids),
|
||||
'meeting_count': len(partner.meeting_ids),
|
||||
|
||||
@@ -128,6 +128,7 @@ class delivery_carrier(osv.osv):
|
||||
grid_id = grid_pool.search(cr, uid, [('carrier_id', '=', record.id)], context=context)
|
||||
if grid_id and not (record.normal_price or record.free_if_more_than):
|
||||
grid_pool.unlink(cr, uid, grid_id, context=context)
|
||||
grid_id = None
|
||||
|
||||
# Check that float, else 0.0 is False
|
||||
if not (isinstance(record.normal_price,float) or record.free_if_more_than):
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
<group>
|
||||
<separator string="Action to Perform" colspan="4"/>
|
||||
<field name="action"/>
|
||||
<field name="survey_id" domain="[('state','=','open')]"/>
|
||||
<field name="survey_id"/>
|
||||
</group>
|
||||
<group string="Send to Managers">
|
||||
<field name="send_answer_manager"/>
|
||||
@@ -364,6 +364,7 @@
|
||||
<field name="res_model">hr.evaluation.interview</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" eval="False"/>
|
||||
<field name="domain" eval="False"/> <!-- Force empty -->
|
||||
<field name="context">{"search_default_todo":1,"search_default_user_id":uid}</field>
|
||||
<field name="search_view_id" ref="view_hr_evaluation_interview_search"/>
|
||||
<field name="help" type="html">
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import threading
|
||||
|
||||
from openerp.osv import osv, fields
|
||||
from openerp import tools, SUPERUSER_ID
|
||||
from openerp.tools.translate import _
|
||||
@@ -217,7 +220,14 @@ class mail_notification(osv.Model):
|
||||
}
|
||||
mail_values.update(custom_values)
|
||||
email_ids.append(self.pool.get('mail.mail').create(cr, uid, mail_values, context=context))
|
||||
if force_send and len(chunks) < 2: # for more than 50 followers, use the queue system
|
||||
# NOTE:
|
||||
# 1. for more than 50 followers, use the queue system
|
||||
# 2. do not send emails immediately if the registry is not loaded,
|
||||
# to prevent sending email during a simple update of the database
|
||||
# using the command-line.
|
||||
if force_send and len(chunks) < 2 and \
|
||||
(not self.pool._init or
|
||||
getattr(threading.currentThread(), 'testing', False)):
|
||||
self.pool.get('mail.mail').send(cr, uid, email_ids, context=context)
|
||||
return True
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ def format_tz(pool, cr, uid, dt, tz=False, format=False, context=None):
|
||||
|
||||
fdate = ts.strftime(format_date)
|
||||
ftime = ts.strftime(format_time)
|
||||
return "%s %s (%s)" % (fdate, ftime, tz)
|
||||
return "%s %s%s" % (fdate, ftime, (' (%s)' % tz) if tz else '')
|
||||
|
||||
try:
|
||||
# We use a jinja2 sandboxed environment to render mako templates.
|
||||
@@ -191,7 +191,7 @@ class mail_template(osv.osv):
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
|
||||
records = self.pool[model].browse(cr, uid, res_ids, context=context) or [None]
|
||||
variables = {
|
||||
'format_tz': lambda dt, tz=False, format=False: format_tz(self.pool, cr, uid, dt, tz, format, context),
|
||||
'format_tz': lambda dt, tz=False, format=False, context=context: format_tz(self.pool, cr, uid, dt, tz, format, context),
|
||||
'user': user,
|
||||
'ctx': context, # context kw would clash with mako internals
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ class mrp_repair(osv.osv):
|
||||
\n* The \'Cancelled\' status is used when user cancel repair order.'),
|
||||
'location_id': fields.many2one('stock.location', 'Current Location', select=True, required=True, readonly=True, states={'draft': [('readonly', False)], 'confirmed': [('readonly', True)]}),
|
||||
'location_dest_id': fields.many2one('stock.location', 'Delivery Location', readonly=True, required=True, states={'draft': [('readonly', False)], 'confirmed': [('readonly', True)]}),
|
||||
'lot_id': fields.many2one('stock.production.lot', 'Repaired Lot', domain="[('product_id','=', product_id)]", help="Products repaired are all belonging to this lot"),
|
||||
'lot_id': fields.many2one('stock.production.lot', 'Repaired Lot', domain="[('product_id','=', product_id)]", help="Products repaired are all belonging to this lot", oldname="prodlot_id"),
|
||||
'guarantee_limit': fields.date('Warranty Expiration', help="The warranty expiration limit is computed as: last move date + warranty defined on selected product. If the current date is below the warranty expiration limit, each operation and fee you will add will be set as 'not to invoiced' by default. Note that you can change manually afterwards.", states={'confirmed': [('readonly', True)]}),
|
||||
'operations': fields.one2many('mrp.repair.line', 'repair_id', 'Operation Lines', readonly=True, states={'draft': [('readonly', False)]}, copy=True),
|
||||
'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', help='Pricelist of the selected partner.'),
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<field name="name"/>
|
||||
<field name="date_order"/>
|
||||
<field name="session_id" required="1"/>
|
||||
<field name="partner_id" on_change="onchange_partner_id(partner_id, context)" context="{'search_default_customer':1}" attrs="{'readonly': [('state','=','invoiced')]}"/>
|
||||
<field name="partner_id" on_change="onchange_partner_id(partner_id, context)" domain="[('customer', '=', True)]" context="{'search_default_customer':1}" attrs="{'readonly': [('state','=','invoiced')]}"/>
|
||||
</group>
|
||||
<notebook colspan="4">
|
||||
<page string="Products">
|
||||
|
||||
@@ -373,11 +373,14 @@ openerp.point_of_sale.load_devices = function load_devices(instance,module){ //m
|
||||
scale_read: function(){
|
||||
var self = this;
|
||||
var ret = new $.Deferred();
|
||||
if (self.use_debug_weight) {
|
||||
return (new $.Deferred()).resolve({weight:this.debug_weight, unit:'Kg', info:'ok'});
|
||||
}
|
||||
this.message('scale_read',{})
|
||||
.then(function(weight){
|
||||
ret.resolve(self.use_debug_weight ? self.debug_weight : weight);
|
||||
ret.resolve(weight);
|
||||
}, function(){ //failed to read weight
|
||||
ret.resolve(self.use_debug_weight ? self.debug_weight : {weight:0.0, unit:'Kg', info:'ok'});
|
||||
ret.resolve({weight:0.0, unit:'Kg', info:'ok'});
|
||||
});
|
||||
return ret;
|
||||
},
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<strong>REFERENCES</strong><br />
|
||||
Order number: <strong>${object.name}</strong><br />
|
||||
Order total: <strong>${object.amount_total} ${object.pricelist_id.currency_id.name}</strong><br />
|
||||
Order date: ${object.date_order}<br />
|
||||
Order date: ${format_tz(object.date_order, tz=user.tz, context={'lang':object.partner_id.lang})}<br />
|
||||
% if object.validity_date and object.state in ('draft', 'sent'):
|
||||
Expiration date: ${object.validity_date}<br />
|
||||
% endif
|
||||
|
||||
@@ -122,7 +122,7 @@ class product_uom(osv.osv):
|
||||
_order = "name"
|
||||
_columns = {
|
||||
'name': fields.char('Unit of Measure', required=True, translate=True),
|
||||
'category_id': fields.many2one('product.uom.categ', 'Product Category', required=True, ondelete='cascade',
|
||||
'category_id': fields.many2one('product.uom.categ', 'Unit of Measure Category', required=True, ondelete='cascade',
|
||||
help="Conversion between Units of Measure can only occur if they belong to the same category. The conversion will be made based on the ratios."),
|
||||
'factor': fields.float('Ratio', required=True, digits=0, # force NUMERIC with unlimited precision
|
||||
help='How much bigger or smaller this unit is compared to the reference Unit of Measure for this category:\n'\
|
||||
@@ -492,7 +492,7 @@ class product_template(osv.osv):
|
||||
|
||||
def _get_product_variant_count(self, cr, uid, ids, name, arg, context=None):
|
||||
res = {}
|
||||
for product in self.browse(cr, uid, ids):
|
||||
for product in self.browse(cr, uid, ids, context=context):
|
||||
res[product.id] = len(product.product_variant_ids)
|
||||
return res
|
||||
|
||||
|
||||
@@ -1565,6 +1565,13 @@ class product_product(osv.Model):
|
||||
for product_id in ids
|
||||
}
|
||||
|
||||
def action_view_purchases(self, cr, uid, ids, context=None):
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
result = self.pool['product.template']._get_act_window_dict(cr, uid, 'purchase.action_purchase_line_product_tree', context=context)
|
||||
result['domain'] = "[('product_id','in',[" + ','.join(map(str, ids)) + "])]"
|
||||
return result
|
||||
|
||||
_columns = {
|
||||
'purchase_count': fields.function(_purchase_count, string='# Purchases', type='integer'),
|
||||
}
|
||||
|
||||
@@ -623,7 +623,7 @@
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_purchase_line_product_tree">
|
||||
<field name="context">{'search_default_product_id': active_id, 'default_product_id': active_id}</field>
|
||||
<field name="context">{}</field><!-- force empty -->
|
||||
<field name="name">Purchases</field>
|
||||
<field name="res_model">purchase.order.line</field>
|
||||
<field name="view_id" ref="purchase_order_line_tree"/>
|
||||
@@ -665,7 +665,7 @@
|
||||
<field name="groups_id" eval="[(4, ref('purchase.group_purchase_user'))]"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='buttons']" position="inside">
|
||||
<button class="oe_inline oe_stat_button" name="%(action_purchase_line_product_tree)d" type="action"
|
||||
<button class="oe_inline oe_stat_button" name="action_view_purchases" type="object"
|
||||
icon="fa-shopping-cart">
|
||||
<field string="Purchases" name="purchase_count" widget="statinfo"/>
|
||||
</button>
|
||||
|
||||
@@ -11,19 +11,18 @@
|
||||
<div t-if="o.dest_address_id">
|
||||
<div t-field="o.dest_address_id"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
|
||||
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
|
||||
</div>
|
||||
|
||||
<div t-if="not o.dest_address_id and o.picking_type_id and o.picking_type_id.warehouse_id">
|
||||
<span t-field="o.picking_type_id.warehouse_id.name"/>
|
||||
<div t-field="o.picking_type_id.warehouse_id.partner_id"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "phone", "fax"], "no_marker": true}'/>
|
||||
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-5 col-xs-offset-1">
|
||||
<div t-field="o.partner_id"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
|
||||
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -12,18 +12,17 @@
|
||||
<div t-if="o.dest_address_id">
|
||||
<div t-field="o.dest_address_id"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
|
||||
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
|
||||
</div>
|
||||
<div t-if="not o.dest_address_id and o.picking_type_id.warehouse_id">
|
||||
<span t-field="o.picking_type_id.warehouse_id.name"/>
|
||||
<div t-field="o.picking_type_id.warehouse_id.partner_id"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "phone", "fax"], "no_marker": true}'/>
|
||||
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-5 col-xs-offset-1">
|
||||
<div t-field="o.partner_id"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
|
||||
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<strong>REFERENCES</strong><br />
|
||||
Order number: <strong>${object.name}</strong><br />
|
||||
Order total: <strong>${object.amount_total} ${object.pricelist_id.currency_id.name}</strong><br />
|
||||
Order date: ${object.date_order}<br />
|
||||
Order date: ${format_tz(object.date_order, tz=user.tz, context={'lang':object.partner_id.lang})} <br />
|
||||
% if object.validity_date and object.state in ('draft', 'sent'):
|
||||
Expiration date: ${object.validity_date}<br />
|
||||
% endif
|
||||
|
||||
+14
-6
@@ -1239,15 +1239,23 @@ class product_product(osv.Model):
|
||||
_inherit = 'product.product'
|
||||
|
||||
def _sales_count(self, cr, uid, ids, field_name, arg, context=None):
|
||||
SaleOrderLine = self.pool['sale.order.line']
|
||||
return {
|
||||
product_id: SaleOrderLine.search_count(cr,uid, [('product_id', '=', product_id)], context=context)
|
||||
for product_id in ids
|
||||
}
|
||||
r = dict.fromkeys(ids, 0)
|
||||
domain = [
|
||||
('state', 'in', ['waiting_date','progress','manual', 'shipping_except', 'invoice_except', 'done']),
|
||||
('product_id', 'in', ids),
|
||||
]
|
||||
for group in self.pool['sale.report'].read_group(cr, uid, domain, ['product_id','product_uom_qty'], ['product_id'], context=context):
|
||||
r[group['product_id'][0]] = group['product_uom_qty']
|
||||
return r
|
||||
|
||||
def action_view_sales(self, cr, uid, ids, context=None):
|
||||
result = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'sale.action_order_line_product_tree', raise_if_not_found=True)
|
||||
result = self.pool['ir.actions.act_window'].read(cr, uid, [result], context=context)[0]
|
||||
result['domain'] = "[('product_id','in',[" + ','.join(map(str, ids)) + "])]"
|
||||
return result
|
||||
|
||||
_columns = {
|
||||
'sales_count': fields.function(_sales_count, string='# Sales', type='integer'),
|
||||
|
||||
}
|
||||
|
||||
class product_template(osv.Model):
|
||||
|
||||
@@ -504,7 +504,7 @@
|
||||
<field name="filter" eval="True"/>
|
||||
</record>
|
||||
<record id="action_order_line_product_tree" model="ir.actions.act_window">
|
||||
<field name="context">{'search_default_product_id': active_id, 'default_product_id': active_id}</field>
|
||||
<field name="context">{}</field><!-- force empty -->
|
||||
<field name="name">Sales Order Lines</field>
|
||||
<field name="res_model">sale.order.line</field>
|
||||
<field name="view_id" ref="view_order_line_tree"/>
|
||||
@@ -516,7 +516,7 @@
|
||||
<field name="groups_id" eval="[(4, ref('base.group_sale_salesman'))]"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='buttons']" position="inside">
|
||||
<button class="oe_inline oe_stat_button" name="%(action_order_line_product_tree)d"
|
||||
<button class="oe_inline oe_stat_button" name="action_view_sales"
|
||||
type="action" icon="fa-strikethrough">
|
||||
<field string="Sales" name="sales_count" widget="statinfo" />
|
||||
</button>
|
||||
|
||||
@@ -53,7 +53,7 @@ class crm_team(osv.Model):
|
||||
month_begin = date.today().replace(day=1)
|
||||
team_result = [{
|
||||
'value': 0,
|
||||
'tooltip': (month_begin + relativedelta.relativedelta(months=-i)).strftime('%B %Y'),
|
||||
'tooltip': tools.ustr((month_begin + relativedelta.relativedelta(months=-i)).strftime('%B %Y')),
|
||||
} for i in range(self._period_number - 1, -1, -1)]
|
||||
group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context)
|
||||
pattern = tools.DEFAULT_SERVER_DATE_FORMAT if obj.fields_get(cr, uid, groupby_field)[groupby_field]['type'] == 'date' else tools.DEFAULT_SERVER_DATETIME_FORMAT
|
||||
|
||||
@@ -293,8 +293,8 @@ class stock_picking(osv.osv):
|
||||
else:
|
||||
invoice = invoice_obj.browse(cr, uid, invoices[key], context=context)
|
||||
if not invoice.origin or invoice_vals['origin'] not in invoice.origin.split(', '):
|
||||
origin = filter(None, [invoice.origin, invoice_vals['origin']])
|
||||
invoice.write({'origin': ', '.join(origin)})
|
||||
invoice_origin = filter(None, [invoice.origin, invoice_vals['origin']])
|
||||
invoice.write({'origin': ', '.join(invoice_origin)})
|
||||
|
||||
invoice_line_vals = move_obj._get_invoice_line_vals(cr, uid, move, partner, inv_type, context=context)
|
||||
invoice_line_vals['invoice_id'] = invoices[key]
|
||||
|
||||
@@ -17,4 +17,5 @@ This module provides the core of the OpenERP Web Client.
|
||||
'qweb' : [
|
||||
"static/src/xml/*.xml",
|
||||
],
|
||||
'bootstrap': True, # load translations for login screen
|
||||
}
|
||||
|
||||
@@ -1108,7 +1108,7 @@ class Binary(http.Controller):
|
||||
content_type = res['file_type']
|
||||
else:
|
||||
res = Model.default_get(cr, uid, fields, context)
|
||||
filecontent = base64.b64decode(res.get(field, ''))
|
||||
filecontent = base64.b64decode(res.get(field) or '')
|
||||
if not filecontent:
|
||||
return request.not_found()
|
||||
else:
|
||||
@@ -1136,7 +1136,7 @@ class Binary(http.Controller):
|
||||
if filename_field:
|
||||
fields.append(filename_field)
|
||||
if data:
|
||||
res = { field: data }
|
||||
res = {field: data, filename_field: jdata.get('filename', None)}
|
||||
elif id:
|
||||
fields.append('file_type')
|
||||
res = Model.read([int(id)], fields, context)[0]
|
||||
@@ -1144,7 +1144,7 @@ class Binary(http.Controller):
|
||||
content_type = res['file_type']
|
||||
else:
|
||||
res = Model.default_get(fields, context)
|
||||
filecontent = base64.b64decode(res.get(field, ''))
|
||||
filecontent = base64.b64decode(res.get(field) or '')
|
||||
if not filecontent:
|
||||
raise ValueError(_("No content found for field '%s' on '%s:%s'") %
|
||||
(field, model, id))
|
||||
|
||||
@@ -390,4 +390,9 @@ instance.web.normalize_format_table = {
|
||||
};
|
||||
var _normalize_format_cache = {};
|
||||
|
||||
instance.web.float_is_zero = function(value, decimals){
|
||||
epsilon = Math.pow(10, -decimals);
|
||||
return Math.abs(instance.web.round_precision(value, epsilon)) < epsilon;
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
@@ -478,6 +478,11 @@ openerp.PropertiesMixin = _.extend({}, openerp.EventDispatcherMixin, {
|
||||
var tmp = self.__getterSetterInternalMap[key];
|
||||
if (tmp === val)
|
||||
return;
|
||||
if (self.field && self.field.type === 'float' && tmp && val){
|
||||
var precision = self.field.digits ? self.field.digits[1] : 2;
|
||||
if (openerp.web.float_is_zero(tmp - val, precision))
|
||||
return;
|
||||
}
|
||||
changed = true;
|
||||
self.__getterSetterInternalMap[key] = val;
|
||||
if (! options.silent)
|
||||
|
||||
@@ -1233,12 +1233,13 @@ instance.web.search.SelectionField = instance.web.search.Field.extend(/** @lends
|
||||
var results = _(this.attrs.selection).chain()
|
||||
.filter(function (sel) {
|
||||
var value = sel[0], label = sel[1];
|
||||
if (!value) { return false; }
|
||||
if (value === undefined || !label) { return false; }
|
||||
return label.toLowerCase().indexOf(needle.toLowerCase()) !== -1;
|
||||
})
|
||||
.map(function (sel) {
|
||||
return {
|
||||
label: _.escape(sel[1]),
|
||||
indent: true,
|
||||
facet: facet_from(self, sel)
|
||||
};
|
||||
}).value();
|
||||
|
||||
@@ -5671,14 +5671,17 @@ instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(instance.
|
||||
} else {
|
||||
instance.web.blockUI();
|
||||
var c = instance.webclient.crashmanager;
|
||||
var filename_fieldname = this.node.attrs.filename;
|
||||
var filename_field = this.view.fields && this.view.fields[filename_fieldname];
|
||||
this.session.get_file({
|
||||
url: '/web/binary/saveas_ajax',
|
||||
data: {data: JSON.stringify({
|
||||
model: this.view.dataset.model,
|
||||
id: (this.view.datarecord.id || ''),
|
||||
field: this.name,
|
||||
filename_field: (this.node.attrs.filename || ''),
|
||||
filename_field: (filename_fieldname || ''),
|
||||
data: instance.web.form.is_bin_size(value) ? null : value,
|
||||
filename: filename_field ? filename_field.get('value') : null,
|
||||
context: this.view.dataset.get_context()
|
||||
})},
|
||||
complete: instance.web.unblockUI,
|
||||
|
||||
@@ -2230,7 +2230,7 @@ instance.web.list.Column = instance.web.Class.extend({
|
||||
}
|
||||
if (attrs.invisible) { return ''; }
|
||||
|
||||
if (!row_data[this.id]) {
|
||||
if (!(row_data[this.id] && row_data[this.id].value)) {
|
||||
return options.value_if_empty === undefined
|
||||
? ''
|
||||
: options.value_if_empty;
|
||||
|
||||
@@ -649,7 +649,7 @@
|
||||
var form = this.editor.form;
|
||||
var last_field = _(form.fields_order).chain()
|
||||
.map(function (name) { return form.fields[name]; })
|
||||
.filter(function (field) { return field.$el.is(':visible'); })
|
||||
.filter(function (field) { return field.$el.is(':visible') && !field.get('effective_readonly'); })
|
||||
.last()
|
||||
.value();
|
||||
// tabbed from last field in form
|
||||
|
||||
@@ -702,25 +702,33 @@ openerp.web_calendar = function(instance) {
|
||||
}
|
||||
|
||||
if (!self.useContacts) { // If we use all peoples displayed in the current month as filter in sidebars
|
||||
var filter_value;
|
||||
var filter_item;
|
||||
|
||||
self.now_filter_ids = [];
|
||||
|
||||
var color_field = self.fields[self.color_field];
|
||||
_.each(events, function (e) {
|
||||
filter_value = e[self.color_field][0];
|
||||
if (!self.all_filters[e[self.color_field][0]]) {
|
||||
var key,val = null;
|
||||
if (self.color_field.type == "selection") {
|
||||
key = e[self.color_field];
|
||||
val = _.find( self.color_field.selection, function(name){ return name[0] === key;});
|
||||
}
|
||||
else {
|
||||
key = e[self.color_field][0];
|
||||
val = e[self.color_field];
|
||||
}
|
||||
if (!self.all_filters[key]) {
|
||||
filter_item = {
|
||||
value: filter_value,
|
||||
label: e[self.color_field][1],
|
||||
color: self.get_color(filter_value),
|
||||
value: key,
|
||||
label: val[1],
|
||||
color: self.get_color(key),
|
||||
avatar_model: (_.str.toBoolElse(self.avatar_filter, true) ? self.avatar_filter : false ),
|
||||
is_checked: true
|
||||
};
|
||||
self.all_filters[e[self.color_field][0]] = filter_item;
|
||||
self.all_filters[key] = filter_item;
|
||||
}
|
||||
if (! _.contains(self.now_filter_ids, filter_value)) {
|
||||
self.now_filter_ids.push(filter_value);
|
||||
if (! _.contains(self.now_filter_ids, key)) {
|
||||
self.now_filter_ids.push(key);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -729,7 +737,8 @@ openerp.web_calendar = function(instance) {
|
||||
self.sidebar.filter.set_filters();
|
||||
|
||||
events = $.map(events, function (e) {
|
||||
if (_.contains(self.now_filter_ids,e[self.color_field][0]) && self.all_filters[e[self.color_field][0]].is_checked) {
|
||||
var key = self.color_field.type == "selection" ? e[self.color_field] : e[self.color_field][0];
|
||||
if (_.contains(self.now_filter_ids, key) && self.all_filters[key].is_checked) {
|
||||
return e;
|
||||
}
|
||||
return null;
|
||||
@@ -751,10 +760,7 @@ openerp.web_calendar = function(instance) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
var all_attendees = $.map(events, function (e) { return e[self.attendee_people]; });
|
||||
all_attendees = _.chain(all_attendees).flatten().uniq().value();
|
||||
|
||||
@@ -1391,7 +1397,9 @@ openerp.web_calendar = function(instance) {
|
||||
});
|
||||
instance.web_calendar.SidebarFilter = instance.web.Widget.extend({
|
||||
events: {
|
||||
'change input:checkbox': 'filter_click'
|
||||
'change input:checkbox': 'filter_click',
|
||||
'click span.color_filter': 'select_previous',
|
||||
|
||||
},
|
||||
init: function(parent, view) {
|
||||
this._super(parent);
|
||||
@@ -1422,10 +1430,13 @@ openerp.web_calendar = function(instance) {
|
||||
if (self.view.all_filters[0] && e.target.value == self.view.all_filters[0].value) {
|
||||
self.view.all_filters[0].is_checked = e.target.checked;
|
||||
} else {
|
||||
self.view.all_filters[parseInt(e.target.value)].is_checked = e.target.checked;
|
||||
self.view.all_filters[e.target.value].is_checked = e.target.checked;
|
||||
}
|
||||
self.view.$calendar.fullCalendar('refetchEvents');
|
||||
},
|
||||
select_previous: function(e) {
|
||||
$(e.target).siblings('input').trigger('click');
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<t t-name="CalendarView.sidebar.responsible">
|
||||
<div class="oe_calendar_all_responsibles">
|
||||
<div t-foreach="filters" class="oe_calendar_responsible" >
|
||||
<t t-if="filters_value.value>=-1"> <!-- don't display if no value given (or undefined) -->
|
||||
<t t-if="filters_value.value"> <!-- don't display if no value given (or undefined) -->
|
||||
<input type="checkbox" name="selection" t-att-value="filters_value.value" checked="checked" />
|
||||
<t t-if="filters_value.value == -1">
|
||||
<span><i t-attf-class="fa fa-users fa-fw cal_avatar" ></i></span>
|
||||
@@ -35,7 +35,7 @@
|
||||
<t t-if="(filters_value.value != -1) && filters_value.avatar_model ">
|
||||
<img t-att-src="'/web/binary/image?model=' + filters_value.avatar_model + '&field=image_small&id=' + filters_value.value" class="cal_avatar"/>
|
||||
</t>
|
||||
<span id="color_filter" t-attf-class="cal_opacity underline_color_#{filters_value.color}" ><t t-esc="filters_value.label" /></span>
|
||||
<span t-attf-class="color_filter cal_opacity underline_color_#{filters_value.color}" ><t t-esc="filters_value.label" /></span>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -213,17 +213,12 @@ class WebsiteBlog(http.Controller):
|
||||
tags = tag_obj.browse(cr, uid, tag_obj.search(cr, uid, [], context=context), context=context)
|
||||
|
||||
# Find next Post
|
||||
visited_blogs = request.httprequest.cookies.get('visited_blogs') or ''
|
||||
visited_ids = filter(None, visited_blogs.split(','))
|
||||
visited_ids = map(lambda x: int(x), visited_ids)
|
||||
if blog_post.id not in visited_ids:
|
||||
visited_ids.append(blog_post.id)
|
||||
next_post_id = blog_post_obj.search(cr, uid, [
|
||||
('id', 'not in', visited_ids),
|
||||
], order='ranking desc', limit=1, context=context)
|
||||
if not next_post_id:
|
||||
next_post_id = blog_post_obj.search(cr, uid, [('id', '!=', blog.id)], order='ranking desc', limit=1, context=context)
|
||||
next_post = next_post_id and blog_post_obj.browse(cr, uid, next_post_id[0], context=context) or False
|
||||
all_post_ids = blog_post_obj.search(cr, uid, [('blog_id', '=', blog.id)], context=context)
|
||||
# should always return at least the current post
|
||||
current_blog_post_index = all_post_ids.index(blog_post.id)
|
||||
next_post_id = all_post_ids[0 if current_blog_post_index == len(all_post_ids) - 1 \
|
||||
else current_blog_post_index + 1]
|
||||
next_post = next_post_id and blog_post_obj.browse(cr, uid, next_post_id, context=context) or False
|
||||
|
||||
values = {
|
||||
'tags': tags,
|
||||
@@ -240,7 +235,6 @@ class WebsiteBlog(http.Controller):
|
||||
'comments': comments,
|
||||
}
|
||||
response = request.website.render("website_blog.blog_post_complete", values)
|
||||
response.set_cookie('visited_blogs', ','.join(map(str, visited_ids)))
|
||||
|
||||
request.session[request.session_id] = request.session.get(request.session_id, [])
|
||||
if not (blog_post.id in request.session[request.session_id]):
|
||||
|
||||
@@ -138,6 +138,8 @@ class BlogPost(osv.Model):
|
||||
:return result: (html, mappin), where html is the updated html with ID
|
||||
and mapping is a list of (old_ID, new_ID), where old_ID
|
||||
is None is the paragraph is a new one. """
|
||||
|
||||
existing_attributes = []
|
||||
mapping = []
|
||||
if not html:
|
||||
return html, mapping
|
||||
@@ -145,7 +147,6 @@ class BlogPost(osv.Model):
|
||||
tags = ['p']
|
||||
if attribute is None:
|
||||
attribute = 'data-unique-id'
|
||||
counter = 0
|
||||
|
||||
# form a tree
|
||||
root = lxml.html.fragment_fromstring(html, create_parent='div')
|
||||
@@ -156,17 +157,22 @@ class BlogPost(osv.Model):
|
||||
# - img src -> check URL
|
||||
# - a href -> check URL
|
||||
for node in root.iter():
|
||||
if not node.tag in tags:
|
||||
if node.tag not in tags:
|
||||
continue
|
||||
ancestor_tags = [parent.tag for parent in node.iterancestors()]
|
||||
if ancestor_tags:
|
||||
ancestor_tags.pop()
|
||||
ancestor_tags.append('counter_%s' % counter)
|
||||
new_attribute = '/'.join(reversed(ancestor_tags))
|
||||
|
||||
old_attribute = node.get(attribute)
|
||||
node.set(attribute, new_attribute)
|
||||
mapping.append((old_attribute, counter))
|
||||
counter += 1
|
||||
new_attribute = old_attribute
|
||||
if old_attribute in existing_attributes:
|
||||
if ancestor_tags:
|
||||
ancestor_tags.pop()
|
||||
counter = random.randint(10000, 99999)
|
||||
ancestor_tags.append('counter_%s' % counter)
|
||||
new_attribute = '/'.join(reversed(ancestor_tags))
|
||||
node.set(attribute, new_attribute)
|
||||
|
||||
existing_attributes.append(new_attribute)
|
||||
mapping.append((old_attribute, new_attribute))
|
||||
|
||||
html = lxml.html.tostring(root, pretty_print=False, method='html')
|
||||
# this is ugly, but lxml/etree tostring want to put everything in a 'div' that breaks the editor -> remove that
|
||||
@@ -179,12 +185,18 @@ class BlogPost(osv.Model):
|
||||
content = self.browse(cr, uid, id, context=context).content
|
||||
if content is False:
|
||||
return content
|
||||
|
||||
content, mapping = self.html_tag_nodes(content, attribute='data-chatter-id', tags=['p'], context=context)
|
||||
for old_attribute, new_attribute in mapping:
|
||||
if not old_attribute:
|
||||
continue
|
||||
msg_ids = self.pool['mail.message'].search(cr, SUPERUSER_ID, [('path', '=', old_attribute)], context=context)
|
||||
self.pool['mail.message'].write(cr, SUPERUSER_ID, msg_ids, {'path': new_attribute}, context=context)
|
||||
if id: # not creating
|
||||
existing = [x[0] for x in mapping if x[0]]
|
||||
msg_ids = self.pool['mail.message'].search(cr, SUPERUSER_ID, [
|
||||
('res_id', '=', id),
|
||||
('model', '=', self._name),
|
||||
('path', 'not in', existing),
|
||||
('path', '!=', False)
|
||||
], context=context)
|
||||
self.pool['mail.message'].unlink(cr, SUPERUSER_ID, msg_ids, context=context)
|
||||
|
||||
return content
|
||||
|
||||
if isinstance(ids, (int, long)):
|
||||
@@ -218,12 +230,13 @@ class BlogPost(osv.Model):
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
if 'content' in vals:
|
||||
vals['content'] = self._postproces_content(cr, uid, None, vals['content'], context=context)
|
||||
vals['content'] = self._postproces_content(cr, uid, ids[0], vals['content'], context=context)
|
||||
result = super(BlogPost, self).write(cr, uid, ids, vals, context)
|
||||
self._check_for_publication(cr, uid, ids, vals, context=context)
|
||||
return result
|
||||
|
||||
|
||||
|
||||
class Website(osv.Model):
|
||||
_inherit = "website"
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
$('.comment_delete').on('click', function (ev) {
|
||||
ev.preventDefault();
|
||||
var $link = $(ev.currentTarget);
|
||||
openerp.jsonRpc($link.data('href'), 'call', {}).then(function (data) {
|
||||
openerp.jsonRpc($link.parent('form').attr('action'), 'call', {}).then(function (data) {
|
||||
$link.parents('.comment').first().remove();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -123,14 +123,17 @@ class website_sale(http.Controller):
|
||||
cr, uid, context, pool = request.cr, request.uid, request.context, request.registry
|
||||
currency_obj = pool['res.currency']
|
||||
attribute_value_ids = []
|
||||
visible_attrs = set(l.attribute_id.id
|
||||
for l in product.attribute_line_ids
|
||||
if len(l.value_ids) > 1)
|
||||
if request.website.pricelist_id.id != context['pricelist']:
|
||||
website_currency_id = request.website.currency_id.id
|
||||
currency_id = self.get_pricelist().currency_id.id
|
||||
for p in product.product_variant_ids:
|
||||
price = currency_obj.compute(cr, uid, website_currency_id, currency_id, p.lst_price)
|
||||
attribute_value_ids.append([p.id, [v.id for v in p.attribute_value_ids if len(v.attribute_id.value_ids) > 1], p.price, price])
|
||||
attribute_value_ids.append([p.id, [v.id for v in p.attribute_value_ids if v.attribute_id.id in visible_attrs], p.price, price])
|
||||
else:
|
||||
attribute_value_ids = [[p.id, [v.id for v in p.attribute_value_ids if len(v.attribute_id.value_ids) > 1], p.price, p.lst_price]
|
||||
attribute_value_ids = [[p.id, [v.id for v in p.attribute_value_ids if v.attribute_id.id in visible_attrs], p.price, p.lst_price]
|
||||
for p in product.product_variant_ids]
|
||||
|
||||
return attribute_value_ids
|
||||
@@ -837,13 +840,20 @@ class website_sale(http.Controller):
|
||||
|
||||
# send the email
|
||||
if email_act and email_act.get('context'):
|
||||
composer_obj = request.registry['mail.compose.message']
|
||||
composer_values = {}
|
||||
email_ctx = email_act['context']
|
||||
public_id = request.website.user_id.id
|
||||
if uid == public_id:
|
||||
template_values = [
|
||||
email_ctx.get('default_template_id'),
|
||||
email_ctx.get('default_composition_mode'),
|
||||
email_ctx.get('default_model'),
|
||||
email_ctx.get('default_res_id'),
|
||||
]
|
||||
composer_values.update(composer_obj.onchange_template_id(cr, SUPERUSER_ID, None, *template_values, context=context).get('value', {}))
|
||||
if not composer_values.get('email_from') and uid == request.website.user_id.id:
|
||||
composer_values['email_from'] = request.website.user_id.company_id.email
|
||||
composer_id = request.registry['mail.compose.message'].create(cr, SUPERUSER_ID, composer_values, context=email_ctx)
|
||||
request.registry['mail.compose.message'].send_mail(cr, SUPERUSER_ID, [composer_id], context=email_ctx)
|
||||
composer_id = composer_obj.create(cr, SUPERUSER_ID, composer_values, context=email_ctx)
|
||||
composer_obj.send_mail(cr, SUPERUSER_ID, [composer_id], context=email_ctx)
|
||||
|
||||
# clean context and session, then redirect to the confirmation page
|
||||
request.website.sale_reset(context=context)
|
||||
|
||||
Vendored
+3
-1
@@ -26,7 +26,7 @@
|
||||
{%- endblock -%}
|
||||
|
||||
{%- block content -%}
|
||||
<div class="document-super {% if meta is defined %}{{ meta.classes }}{% endif %}">
|
||||
<div class="document-super {% if meta is defined %}{{ meta.classes }}{% endif %} {% if render_sidebar %}with-sidebar{% endif %}">
|
||||
{{ super() }}
|
||||
</div>
|
||||
{%- endblock -%}
|
||||
@@ -34,6 +34,7 @@
|
||||
{# sidebar to the left, except sidebar1 is *outside* div.document where
|
||||
sidebar2 is inside, so inject sidebar into document block #}
|
||||
{%- block document -%}
|
||||
{%- if render_sidebar -%}
|
||||
<div class="sphinxsidebar">
|
||||
<div class="sphinxsidebarwrapper">
|
||||
{{ toctree(maxdepth=4, collapse=False, includehidden=True,
|
||||
@@ -45,6 +46,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{%- endif -%}
|
||||
{{ super() }}
|
||||
{%- endblock -%}
|
||||
{%- block sidebar1 -%}{%- endblock -%}
|
||||
|
||||
Vendored
+15
@@ -6247,6 +6247,18 @@ body {
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.documentwrapper {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.with-sidebar .documentwrapper {
|
||||
position: relative;
|
||||
min-height: 1px;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.with-sidebar .documentwrapper {
|
||||
float: left;
|
||||
width: 75%;
|
||||
}
|
||||
@@ -6611,6 +6623,9 @@ div.section > h2 {
|
||||
padding-top: 20px;
|
||||
margin-top: 0;
|
||||
}
|
||||
.body {
|
||||
position: relative;
|
||||
}
|
||||
.body > .section > .section {
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
+9
-2
@@ -37,6 +37,9 @@ body {
|
||||
}
|
||||
|
||||
.documentwrapper {
|
||||
.make-md-column(12);
|
||||
}
|
||||
.with-sidebar .documentwrapper {
|
||||
.make-md-column(9);
|
||||
}
|
||||
.sphinxsidebar {
|
||||
@@ -403,8 +406,12 @@ div.section > h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
// ~docs-section
|
||||
.body > .section > .section {
|
||||
margin-bottom: 60px;
|
||||
.body {
|
||||
// otherwise main section right column content may not be visible
|
||||
position: relative;
|
||||
> .section > .section {
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
.literal:extend(code) {}
|
||||
|
||||
+27
-41
@@ -19,29 +19,19 @@ Modules customize the behavior of an Odoo installation, either by adding new
|
||||
behaviors or by altering existing ones (including behaviors added by other
|
||||
modules).
|
||||
|
||||
First let's create a *module directory* which will contain a single module in
|
||||
our case but may store multiple related (a project's) or not really related
|
||||
(a company's) modules:
|
||||
:ref:`Odoo's scaffolding <reference/cmdline/scaffold>` can setup a basic
|
||||
module to quickly get started, simply invoke:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ mkdir my-modules
|
||||
$ ./odoo.py scaffold Academy my-modules
|
||||
|
||||
then let's create the module's own directory:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ mkdir my-modules/academy
|
||||
|
||||
An Odoo module is a valid `Python package
|
||||
<http://docs.python.org/2/tutorial/modules.html#packages>`_ so it needs an
|
||||
empty ``__init__.py`` file.
|
||||
|
||||
Finally the mark of an Odoo module is the
|
||||
:ref:`manifest file <reference/module/manifest>`, a Python dictionary describing
|
||||
various module metadata.
|
||||
This will automatically create a ``my-modules`` *module directory* with an
|
||||
``academy`` module inside. The directory can be an existing module directory
|
||||
if you want, but the module name must be unique for the directory.
|
||||
|
||||
.. patch::
|
||||
:hidden:
|
||||
|
||||
A demonstration module
|
||||
======================
|
||||
@@ -52,30 +42,25 @@ Although it does absolutely nothing yet we can install it:
|
||||
|
||||
* start the Odoo server
|
||||
|
||||
.. code-block:: console
|
||||
.. code-block:: console
|
||||
|
||||
$ ./odoo.py --addons-path addons,my-modules
|
||||
$ ./odoo.py --addons-path addons,my-modules
|
||||
|
||||
* go to http://localhost:8069
|
||||
* create a new database including demonstration data
|
||||
* to go :menuselection:`Settings --> Modules --> Installed Modules`
|
||||
* to go :menuselection:`Settings --> Modules --> Local Modules`
|
||||
* in the top-right corner remove the *Installed* filter and search for
|
||||
*academy*
|
||||
* click the :guilabel:`Install` button for the *Academy* module
|
||||
|
||||
.. seealso::
|
||||
|
||||
* In a production development setting, modules should generally be created
|
||||
using :ref:`Odoo's scaffolding <reference/cmdline/scaffold>` rather than by
|
||||
hand
|
||||
|
||||
To the browser
|
||||
==============
|
||||
|
||||
:ref:`Controllers <reference/http/controllers>` interpret browser requests and
|
||||
send data back.
|
||||
|
||||
Add a simple controller and import it (so Odoo can find it):
|
||||
Add a simple controller and ensure it is imported by ``__init__.py`` (so
|
||||
Odoo can find it):
|
||||
|
||||
.. patch::
|
||||
|
||||
@@ -85,8 +70,8 @@ Shut down your server (:kbd:`^C`) then restart it:
|
||||
|
||||
$ ./odoo.py --addons-path addons,my-modules
|
||||
|
||||
and open a page to http://localhost:8069/academy/, you should see your "page"
|
||||
appear:
|
||||
and open a page to http://localhost:8069/academy/academy/, you should see your
|
||||
"page" appear:
|
||||
|
||||
.. figure:: website/helloworld.png
|
||||
|
||||
@@ -100,8 +85,8 @@ display logic. Odoo allows any Python templating system, but provides its
|
||||
own :ref:`QWeb <reference/qweb>` templating system which integrates with other
|
||||
Odoo features.
|
||||
|
||||
Let's create an XML file for our first template, register the template in the
|
||||
manifest and alter the controller to use our template:
|
||||
Create a template and ensure the template file is registered in the
|
||||
``__openerp__.py`` manifest, and alter the controller to use our template:
|
||||
|
||||
.. patch::
|
||||
|
||||
@@ -109,7 +94,7 @@ The templates iterates (``t-foreach``) on all the teachers (passed through the
|
||||
*template context*), and prints each teacher in its own paragraph.
|
||||
|
||||
Finally restart Odoo and update the module's data (to install the template)
|
||||
by going to :menuselection:`Settings --> Modules --> Installed Modules -->
|
||||
by going to :menuselection:`Settings --> Modules --> Local Modules -->
|
||||
Academy` and clicking :guilabel:`Upgrade`.
|
||||
|
||||
.. tip::
|
||||
@@ -121,7 +106,7 @@ Academy` and clicking :guilabel:`Upgrade`.
|
||||
|
||||
$ odoo.py --addons-path addons,my-modules -d academy -u academy
|
||||
|
||||
Going to http://localhost:8069/academy/ should now result in:
|
||||
Going to http://localhost:8069/academy/academy/ should now result in:
|
||||
|
||||
.. image:: website/basic-list.png
|
||||
|
||||
@@ -137,7 +122,8 @@ thereof, so we're now going to move our data to the database.
|
||||
Defining the data model
|
||||
-----------------------
|
||||
|
||||
First define an Odoo model file and import it:
|
||||
Define a teacher model, and ensure it is imported from ``__init__.py`` so it
|
||||
is correctly loaded:
|
||||
|
||||
.. patch::
|
||||
|
||||
@@ -165,7 +151,7 @@ Demonstration data
|
||||
|
||||
The second step is to add some demonstration data to the system so it's
|
||||
possible to test it easily. This is done by adding a ``demo``
|
||||
:ref:`data file <reference/data>` to the manifest:
|
||||
:ref:`data file <reference/data>`, which must be linked from the manifest:
|
||||
|
||||
.. patch::
|
||||
|
||||
@@ -194,8 +180,8 @@ The last step is to alter model and template to use our demonstration data:
|
||||
|
||||
Restart the server and update the module (in order to update the manifest
|
||||
and templates and load the demo file) then navigate to
|
||||
http://localhost:8069/academy/. The page should look little different: names
|
||||
should simply be prefixed by a number (the database identifier for the
|
||||
http://localhost:8069/academy/academy/. The page should look little different:
|
||||
names should simply be prefixed by a number (the database identifier for the
|
||||
teacher).
|
||||
|
||||
Website support
|
||||
@@ -216,9 +202,9 @@ integration and a few other services (e.g. default styling, theming) via the
|
||||
.. patch::
|
||||
|
||||
After restarting the server while updating the module (in order to update the
|
||||
manifest and template) access http://localhost:8069/academy/ should yield a
|
||||
nicer looking page with branding and a number of built-in page elements
|
||||
(top-level menu, footer, …)
|
||||
manifest and template) access http://localhost:8069/academy/academy/ should
|
||||
yield a nicer looking page with branding and a number of built-in page
|
||||
elements (top-level menu, footer, …)
|
||||
|
||||
.. image:: website/layout.png
|
||||
|
||||
@@ -396,7 +382,7 @@ let's also add views so we can see and edit a course's teacher:
|
||||
|
||||
It should also be possible to create new courses directly from a teacher's
|
||||
page, or to see all the courses a teacher gives, so add
|
||||
:class:`the inverse relationship <openerp.fields.One2many` to the *teachers*
|
||||
:class:`the inverse relationship <openerp.fields.One2many>` to the *teachers*
|
||||
model:
|
||||
|
||||
.. patch::
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
# HG changeset patch
|
||||
# Parent 91c0cc5b319e7bf240d359817b8bd044769a4f5c
|
||||
diff -r 91c0cc5b319e -r c77aa2c3341b academy/__openerp__.py
|
||||
--- a/academy/__openerp__.py Mon Aug 11 16:49:10 2014 +0200
|
||||
+++ b/academy/__openerp__.py Mon Aug 11 16:50:01 2014 +0200
|
||||
@@ -8,6 +8,7 @@
|
||||
'depends': ['base'],
|
||||
# data files which are always installed
|
||||
# Parent 0a9ec16d98785205f25868bc23485569a1444cf3
|
||||
|
||||
diff --git a/academy/__openerp__.py b/academy/__openerp__.py
|
||||
--- a/academy/__openerp__.py
|
||||
+++ b/academy/__openerp__.py
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
+ 'ir.model.access.csv',
|
||||
- # 'security/ir.model.access.csv',
|
||||
+ 'security/ir.model.access.csv',
|
||||
'templates.xml',
|
||||
],
|
||||
}
|
||||
diff -r 91c0cc5b319e -r c77aa2c3341b academy/ir.model.access.csv
|
||||
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
|
||||
+++ b/academy/ir.model.access.csv Mon Aug 11 16:50:01 2014 +0200
|
||||
@@ -0,0 +1,2 @@
|
||||
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
# only loaded in demonstration mode
|
||||
diff --git a/academy/security/ir.model.access.csv b/academy/security/ir.model.access.csv
|
||||
--- a/academy/security/ir.model.access.csv
|
||||
+++ b/academy/security/ir.model.access.csv
|
||||
@@ -1,2 +1,2 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
-access_academy_academy,academy.academy,model_academy_academy,,1,0,0,0
|
||||
+access_academy_teachers,access_academy_teachers,model_academy_teachers,,1,0,0,0
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
# HG changeset patch
|
||||
# Parent 78b4476f35cbef86ee4f858daf843e3b932fb9fa
|
||||
diff -r 78b4476f35cb -r d5ad2ad84a5d academy/__init__.py
|
||||
--- a/academy/__init__.py Mon Aug 11 16:09:21 2014 +0200
|
||||
+++ b/academy/__init__.py Mon Aug 11 16:12:38 2014 +0200
|
||||
@@ -0,0 +1,1 @@
|
||||
+from . import controllers
|
||||
diff -r 78b4476f35cb -r d5ad2ad84a5d academy/controllers.py
|
||||
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
|
||||
+++ b/academy/controllers.py Mon Aug 11 16:12:38 2014 +0200
|
||||
@@ -0,0 +1,8 @@
|
||||
+# -*- coding: utf-8 -*-
|
||||
+from openerp import http
|
||||
+
|
||||
# Parent 0a4f5e3206a0738201a40d2d1a88f41fdbbc98bf
|
||||
|
||||
diff --git a/academy/controllers.py b/academy/controllers.py
|
||||
--- a/academy/controllers.py
|
||||
+++ b/academy/controllers.py
|
||||
@@ -1,10 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from openerp import http
|
||||
|
||||
-# class Academy(http.Controller):
|
||||
-# @http.route('/academy/academy/', auth='public')
|
||||
-# def index(self, **kw):
|
||||
-# return "Hello, world"
|
||||
+class Academy(http.Controller):
|
||||
+ @http.route('/academy/', auth='public')
|
||||
+ def index(self):
|
||||
+ return "Hello, world!"
|
||||
+
|
||||
+ @http.route('/academy/academy/', auth='public')
|
||||
+ def index(self, **kw):
|
||||
+ return "Hello, world"
|
||||
|
||||
# @http.route('/academy/academy/objects/', auth='public')
|
||||
# def list(self, **kw):
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
# HG changeset patch
|
||||
# Parent 3e800beb7db0f9f05bfd4db275c86e8903d4f127
|
||||
diff -r 3e800beb7db0 -r 91c0cc5b319e academy/__init__.py
|
||||
--- a/academy/__init__.py Mon Aug 11 16:36:15 2014 +0200
|
||||
+++ b/academy/__init__.py Mon Aug 11 16:49:10 2014 +0200
|
||||
@@ -1,1 +1,2 @@
|
||||
from . import controllers
|
||||
+from . import models
|
||||
diff -r 3e800beb7db0 -r 91c0cc5b319e academy/models.py
|
||||
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
|
||||
+++ b/academy/models.py Mon Aug 11 16:49:10 2014 +0200
|
||||
@@ -0,0 +1,7 @@
|
||||
+from openerp import fields
|
||||
+from openerp import models
|
||||
+
|
||||
# Parent 4082801768b28c2871920e41d23b7879763e763e
|
||||
|
||||
diff --git a/academy/models.py b/academy/models.py
|
||||
--- a/academy/models.py
|
||||
+++ b/academy/models.py
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from openerp import models, fields, api
|
||||
|
||||
-# class academy(models.Model):
|
||||
-# _name = 'academy.academy'
|
||||
+class Teachers(models.Model):
|
||||
+ _name = 'academy.teachers'
|
||||
+
|
||||
|
||||
-# name = fields.Char()
|
||||
+ name = fields.Char()
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
# HG changeset patch
|
||||
# Parent 4814709fe1c52515f5108623c2c8f0bce57ffac0
|
||||
diff -r 4814709fe1c5 -r 216904cc7636 academy/models.py
|
||||
--- a/academy/models.py Tue Aug 12 17:18:59 2014 +0200
|
||||
+++ b/academy/models.py Tue Aug 12 17:24:44 2014 +0200
|
||||
@@ -5,3 +5,4 @@ class Teachers(models.Model):
|
||||
# Parent 926b3fdd1bba025d9543bb85ac40159b392ba13c
|
||||
|
||||
diff --git a/academy/models.py b/academy/models.py
|
||||
--- a/academy/models.py
|
||||
+++ b/academy/models.py
|
||||
@@ -6,3 +6,4 @@ class Teachers(models.Model):
|
||||
_name = 'academy.teachers'
|
||||
|
||||
name = fields.Char()
|
||||
+ biography = fields.Html()
|
||||
diff -r 4814709fe1c5 -r 216904cc7636 academy/templates.xml
|
||||
--- a/academy/templates.xml Tue Aug 12 17:18:59 2014 +0200
|
||||
+++ b/academy/templates.xml Tue Aug 12 17:24:44 2014 +0200
|
||||
diff --git a/academy/templates.xml b/academy/templates.xml
|
||||
--- a/academy/templates.xml
|
||||
+++ b/academy/templates.xml
|
||||
@@ -21,6 +21,7 @@
|
||||
<div class="oe_structure">
|
||||
<div class="container">
|
||||
<h3><t t-esc="person.name"/></h3>
|
||||
+ <div><t t-esc="person.biography"/></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_structure"/>
|
||||
<div class="oe_structure">
|
||||
<div class="container">
|
||||
<h3><t t-esc="person.name"/></h3>
|
||||
+ <div><t t-esc="person.biography"/></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_structure"/>
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
# HG changeset patch
|
||||
# Parent 216904cc7636679adbf1a5a211070e739c59c3a8
|
||||
diff -r 216904cc7636 -r d5b8a2de3e35 academy/templates.xml
|
||||
--- a/academy/templates.xml Tue Aug 12 17:24:44 2014 +0200
|
||||
+++ b/academy/templates.xml Tue Aug 12 17:25:15 2014 +0200
|
||||
# Parent edeaf1ebff99c9b3d7cbe1925ca3d2ece519ba52
|
||||
|
||||
diff --git a/academy/templates.xml b/academy/templates.xml
|
||||
--- a/academy/templates.xml
|
||||
+++ b/academy/templates.xml
|
||||
@@ -20,8 +20,8 @@
|
||||
<div class="oe_structure"/>
|
||||
<div class="oe_structure">
|
||||
<div class="container">
|
||||
- <h3><t t-esc="person.name"/></h3>
|
||||
- <div><t t-esc="person.biography"/></div>
|
||||
+ <h3 t-field="person.name"/>
|
||||
+ <div t-field="person.biography"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_structure"/>
|
||||
<div class="oe_structure"/>
|
||||
<div class="oe_structure">
|
||||
<div class="container">
|
||||
- <h3><t t-esc="person.name"/></h3>
|
||||
- <div><t t-esc="person.biography"/></div>
|
||||
+ <h3 t-field="person.name"/>
|
||||
+ <div t-field="person.biography"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_structure"/>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# HG changeset patch
|
||||
# Parent 957395d27f63dfdb2ea0dacdbe72bc35e724ddcc
|
||||
diff -r 957395d27f63 -r 6106208621f0 academy/models.py
|
||||
--- a/academy/models.py Wed Aug 13 15:06:30 2014 +0200
|
||||
+++ b/academy/models.py Wed Aug 13 16:32:26 2014 +0200
|
||||
@@ -11,6 +11,7 @@ class Teachers(models.Model):
|
||||
# Parent 7156498d3cffb6128e73da2e351f2b5af5f7be63
|
||||
|
||||
diff --git a/academy/models.py b/academy/models.py
|
||||
--- a/academy/models.py
|
||||
+++ b/academy/models.py
|
||||
@@ -12,6 +12,7 @@ class Teachers(models.Model):
|
||||
|
||||
class Courses(models.Model):
|
||||
_name = 'academy.courses'
|
||||
@@ -11,9 +13,9 @@ diff -r 957395d27f63 -r 6106208621f0 academy/models.py
|
||||
|
||||
name = fields.Char()
|
||||
teacher_id = fields.Many2one('academy.teachers', string="Teacher")
|
||||
diff -r 957395d27f63 -r 6106208621f0 academy/views.xml
|
||||
--- a/academy/views.xml Wed Aug 13 15:06:30 2014 +0200
|
||||
+++ b/academy/views.xml Wed Aug 13 16:32:26 2014 +0200
|
||||
diff --git a/academy/views.xml b/academy/views.xml
|
||||
--- a/academy/views.xml
|
||||
+++ b/academy/views.xml
|
||||
@@ -60,6 +60,10 @@
|
||||
<label for="teacher_id"/>
|
||||
<field name="teacher_id"/>
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
# HG changeset patch
|
||||
# Parent 122340f4360287bbc53f54e53ac609a8e7237796
|
||||
diff -r 122340f43602 -r 91ffd360f3ff academy/controllers.py
|
||||
--- a/academy/controllers.py Tue Aug 12 16:52:38 2014 +0200
|
||||
+++ b/academy/controllers.py Tue Aug 12 17:02:14 2014 +0200
|
||||
@@ -9,7 +9,9 @@ class Academy(http.Controller):
|
||||
'teachers': Teachers.search([]),
|
||||
# Parent 1f9194514fa81c49b9bc7922a010513d816a0c2e
|
||||
|
||||
diff --git a/academy/controllers.py b/academy/controllers.py
|
||||
--- a/academy/controllers.py
|
||||
+++ b/academy/controllers.py
|
||||
@@ -9,9 +9,11 @@ class Academy(http.Controller):
|
||||
'teachers': Teachers.search([])
|
||||
})
|
||||
|
||||
- @http.route('/academy/<int:id>/', auth='public', website=True)
|
||||
@@ -16,24 +18,27 @@ diff -r 122340f43602 -r 91ffd360f3ff academy/controllers.py
|
||||
+ 'person': teacher
|
||||
+ })
|
||||
|
||||
diff -r 122340f43602 -r 91ffd360f3ff academy/templates.xml
|
||||
--- a/academy/templates.xml Tue Aug 12 16:52:38 2014 +0200
|
||||
+++ b/academy/templates.xml Tue Aug 12 17:02:14 2014 +0200
|
||||
@@ -11,4 +11,17 @@
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
+
|
||||
+ <template id="biography">
|
||||
+ <t t-call="website.layout">
|
||||
+ <t t-set="title">Academy</t>
|
||||
+ <div class="oe_structure"/>
|
||||
+ <div class="oe_structure">
|
||||
+ <div class="container">
|
||||
+ <p><t t-esc="person.id"/> <t t-esc="person.name"/></p>
|
||||
+ </div>
|
||||
+ </div>
|
||||
+ <div class="oe_structure"/>
|
||||
+ </t>
|
||||
+ </template>
|
||||
</data></openerp>
|
||||
|
||||
# @http.route('/academy/academy/objects/', auth='public')
|
||||
diff --git a/academy/templates.xml b/academy/templates.xml
|
||||
--- a/academy/templates.xml
|
||||
+++ b/academy/templates.xml
|
||||
@@ -12,6 +12,18 @@
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
+ <template id="biography">
|
||||
+ <t t-call="website.layout">
|
||||
+ <t t-set="title">Academy</t>
|
||||
+ <div class="oe_structure"/>
|
||||
+ <div class="oe_structure">
|
||||
+ <div class="container">
|
||||
+ <p><t t-esc="person.id"/> <t t-esc="person.name"/></p>
|
||||
+ </div>
|
||||
+ </div>
|
||||
+ <div class="oe_structure"/>
|
||||
+ </t>
|
||||
+ </template>
|
||||
<!-- <template id="object"> -->
|
||||
<!-- <h1><t t-esc="object.display_name"/></h1> -->
|
||||
<!-- <dl> -->
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
# HG changeset patch
|
||||
# Parent c3705f93fcea86a279f2c31204cc0c2914e784d8
|
||||
# Parent 8f2d190d879cdf6464bd61f7994b8b582befcafd
|
||||
|
||||
diff --git a/academy/ir.model.access.csv b/academy/ir.model.access.csv
|
||||
--- a/academy/ir.model.access.csv
|
||||
+++ b/academy/ir.model.access.csv
|
||||
@@ -1,2 +1,3 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_academy_teachers,access_academy_teachers,model_academy_teachers,,1,0,0,0
|
||||
+access_academy_courses,access_academy_courses,model_academy_courses,,1,0,0,0
|
||||
diff --git a/academy/models.py b/academy/models.py
|
||||
--- a/academy/models.py
|
||||
+++ b/academy/models.py
|
||||
@@ -6,3 +6,9 @@ class Teachers(models.Model):
|
||||
@@ -7,3 +7,9 @@ class Teachers(models.Model):
|
||||
|
||||
name = fields.Char()
|
||||
biography = fields.Html()
|
||||
@@ -21,3 +15,11 @@ diff --git a/academy/models.py b/academy/models.py
|
||||
+
|
||||
+ name = fields.Char()
|
||||
+ teacher_id = fields.Many2one('academy.teachers', string="Teacher")
|
||||
diff --git a/academy/security/ir.model.access.csv b/academy/security/ir.model.access.csv
|
||||
--- a/academy/security/ir.model.access.csv
|
||||
+++ b/academy/security/ir.model.access.csv
|
||||
@@ -1,2 +1,3 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_academy_teachers,access_academy_teachers,model_academy_teachers,,1,0,0,0
|
||||
+access_academy_courses,access_academy_courses,model_academy_courses,,1,0,0,0
|
||||
\ No newline at end of file
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
# HG changeset patch
|
||||
# Parent 1708c59c9731207e06ca84529c839c571e622481
|
||||
diff -r 1708c59c9731 -r c706c2ec2832 academy/templates.xml
|
||||
--- a/academy/templates.xml Thu Aug 14 14:53:52 2014 +0200
|
||||
+++ b/academy/templates.xml Thu Aug 14 16:08:10 2014 +0200
|
||||
@@ -28,4 +28,10 @@
|
||||
<div class="oe_structure"/>
|
||||
</t>
|
||||
</template>
|
||||
# Parent ac472417df8770ce9b6922968aa86779a86be35c
|
||||
|
||||
diff --git a/academy/templates.xml b/academy/templates.xml
|
||||
--- a/academy/templates.xml
|
||||
+++ b/academy/templates.xml
|
||||
@@ -28,6 +28,13 @@
|
||||
<div class="oe_structure"/>
|
||||
</t>
|
||||
</template>
|
||||
+
|
||||
+ <template id="product_item_hide_no_price" inherit_id="website_sale.products_item">
|
||||
+ <xpath expr="//div[hasclass('product_price')]/b" position="attributes">
|
||||
+ <attribute name="t-if">product.price > 0</attribute>
|
||||
+ </xpath>
|
||||
+ </template>
|
||||
</data></openerp>
|
||||
+ <template id="product_item_hide_no_price" inherit_id="website_sale.products_item">
|
||||
+ <xpath expr="//div[hasclass('product_price')]/b" position="attributes">
|
||||
+ <attribute name="t-if">product.price > 0</attribute>
|
||||
+ </xpath>
|
||||
+ </template>
|
||||
+
|
||||
<!-- <template id="object"> -->
|
||||
<!-- <h1><t t-esc="object.display_name"/></h1> -->
|
||||
<!-- <dl> -->
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
# HG changeset patch
|
||||
# Parent 2ee7212e5de4702dff08d9b5e4268e7dc261d038
|
||||
# Parent 4ca7b848bb60c3573de7ed41a28b4b65186a5725
|
||||
|
||||
diff --git a/academy/__openerp__.py b/academy/__openerp__.py
|
||||
--- a/academy/__openerp__.py
|
||||
+++ b/academy/__openerp__.py
|
||||
@@ -11,6 +11,7 @@
|
||||
'ir.model.access.csv',
|
||||
@@ -27,6 +27,7 @@
|
||||
'security/ir.model.access.csv',
|
||||
'templates.xml',
|
||||
'views.xml',
|
||||
+ 'data.xml',
|
||||
],
|
||||
# data files which are only installed in "demonstration mode"
|
||||
# only loaded in demonstration mode
|
||||
'demo': [
|
||||
diff --git a/academy/data.xml b/academy/data.xml
|
||||
new file mode 100644
|
||||
@@ -26,47 +27,41 @@ new file mode 100644
|
||||
diff --git a/academy/demo.xml b/academy/demo.xml
|
||||
--- a/academy/demo.xml
|
||||
+++ b/academy/demo.xml
|
||||
@@ -8,4 +8,29 @@
|
||||
<record id="vaughn" model="academy.teachers">
|
||||
<field name="name">Lester Vaughn</field>
|
||||
</record>
|
||||
@@ -9,5 +9,30 @@
|
||||
<record id="vaughn" model="academy.teachers">
|
||||
<field name="name">Lester Vaughn</field>
|
||||
</record>
|
||||
+
|
||||
+ <record id="course0" model="product.template">
|
||||
+ <field name="name">Course 0</field>
|
||||
+ <field name="teacher_id" ref="padilla"/>
|
||||
+ <field name="public_categ_ids" eval="[(4, ref('academy.category_courses'), False)]"/>
|
||||
+ <field name="website_published">True</field>
|
||||
+ <field name="list_price" type="float">0</field>
|
||||
+ <field name="type">service</field>
|
||||
+ </record>
|
||||
+ <record id="course1" model="product.template">
|
||||
+ <field name="name">Course 1</field>
|
||||
+ <field name="teacher_id" ref="padilla"/>
|
||||
+ <field name="public_categ_ids" eval="[(4, ref('academy.category_courses'), False)]"/>
|
||||
+ <field name="website_published">True</field>
|
||||
+ <field name="list_price" type="float">0</field>
|
||||
+ <field name="type">service</field>
|
||||
+ </record>
|
||||
+ <record id="course2" model="product.template">
|
||||
+ <field name="name">Course 2</field>
|
||||
+ <field name="teacher_id" ref="vaughn"/>
|
||||
+ <field name="public_categ_ids" eval="[(4, ref('academy.category_courses'), False)]"/>
|
||||
+ <field name="website_published">True</field>
|
||||
+ <field name="list_price" type="float">0</field>
|
||||
+ <field name="type">service</field>
|
||||
+ </record>
|
||||
</data></openerp>
|
||||
diff --git a/academy/ir.model.access.csv b/academy/ir.model.access.csv
|
||||
--- a/academy/ir.model.access.csv
|
||||
+++ b/academy/ir.model.access.csv
|
||||
@@ -1,3 +1,2 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_academy_teachers,access_academy_teachers,model_academy_teachers,,1,0,0,0
|
||||
-access_academy_courses,access_academy_courses,model_academy_courses,,1,0,0,0
|
||||
+ <record id="course0" model="product.template">
|
||||
+ <field name="name">Course 0</field>
|
||||
+ <field name="teacher_id" ref="padilla"/>
|
||||
+ <field name="public_categ_ids" eval="[(4, ref('academy.category_courses'), False)]"/>
|
||||
+ <field name="website_published">True</field>
|
||||
+ <field name="list_price" type="float">0</field>
|
||||
+ <field name="type">service</field>
|
||||
+ </record>
|
||||
+ <record id="course1" model="product.template">
|
||||
+ <field name="name">Course 1</field>
|
||||
+ <field name="teacher_id" ref="padilla"/>
|
||||
+ <field name="public_categ_ids" eval="[(4, ref('academy.category_courses'), False)]"/>
|
||||
+ <field name="website_published">True</field>
|
||||
+ <field name="list_price" type="float">0</field>
|
||||
+ <field name="type">service</field>
|
||||
+ </record>
|
||||
+ <record id="course2" model="product.template">
|
||||
+ <field name="name">Course 2</field>
|
||||
+ <field name="teacher_id" ref="vaughn"/>
|
||||
+ <field name="public_categ_ids" eval="[(4, ref('academy.category_courses'), False)]"/>
|
||||
+ <field name="website_published">True</field>
|
||||
+ <field name="list_price" type="float">0</field>
|
||||
+ <field name="type">service</field>
|
||||
+ </record>
|
||||
</data>
|
||||
</openerp>
|
||||
diff --git a/academy/models.py b/academy/models.py
|
||||
--- a/academy/models.py
|
||||
+++ b/academy/models.py
|
||||
@@ -7,11 +7,9 @@ class Teachers(models.Model):
|
||||
@@ -8,11 +8,9 @@ class Teachers(models.Model):
|
||||
name = fields.Char()
|
||||
biography = fields.Html()
|
||||
|
||||
@@ -80,6 +75,16 @@ diff --git a/academy/models.py b/academy/models.py
|
||||
|
||||
- name = fields.Char()
|
||||
teacher_id = fields.Many2one('academy.teachers', string="Teacher")
|
||||
diff --git a/academy/security/ir.model.access.csv b/academy/security/ir.model.access.csv
|
||||
--- a/academy/security/ir.model.access.csv
|
||||
+++ b/academy/security/ir.model.access.csv
|
||||
@@ -1,3 +1,2 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
-access_academy_teachers,access_academy_teachers,model_academy_teachers,,1,0,0,0
|
||||
-access_academy_courses,access_academy_courses,model_academy_courses,,1,0,0,0
|
||||
\ No newline at end of file
|
||||
+access_academy_teachers,access_academy_teachers,model_academy_teachers,,1,0,0,0
|
||||
\ No newline at end of file
|
||||
diff --git a/academy/views.xml b/academy/views.xml
|
||||
--- a/academy/views.xml
|
||||
+++ b/academy/views.xml
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
# HG changeset patch
|
||||
# Parent c706c2ec28328a65a8aee6a7ba7d06896468c1e3
|
||||
diff -r c706c2ec2832 -r f85b481451bb academy/templates.xml
|
||||
--- a/academy/templates.xml Thu Aug 14 16:08:10 2014 +0200
|
||||
+++ b/academy/templates.xml Thu Aug 14 16:30:49 2014 +0200
|
||||
@@ -34,4 +34,8 @@
|
||||
<attribute name="t-if">product.price > 0</attribute>
|
||||
</xpath>
|
||||
</template>
|
||||
# Parent 053262442acdbe3c803c29ce35002ec69452c5fd
|
||||
|
||||
diff --git a/academy/templates.xml b/academy/templates.xml
|
||||
--- a/academy/templates.xml
|
||||
+++ b/academy/templates.xml
|
||||
@@ -35,6 +35,10 @@
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
+ <record id="website_sale.products_categories" model="ir.ui.view">
|
||||
+ <field name="active" eval="True"/>
|
||||
+ </record>
|
||||
+
|
||||
+ <record id="website_sale.products_categories" model="ir.ui.view">
|
||||
+ <field name="active" eval="True"/>
|
||||
+ </record>
|
||||
</data></openerp>
|
||||
<!-- <template id="object"> -->
|
||||
<!-- <h1><t t-esc="object.display_name"/></h1> -->
|
||||
<!-- <dl> -->
|
||||
|
||||
@@ -1,29 +1,30 @@
|
||||
# HG changeset patch
|
||||
# Parent f0a3629abb17ca9f4dddd8885321d9ca5de14b55
|
||||
diff -r f0a3629abb17 -r 7ae0db4c2ddf academy/controllers.py
|
||||
--- a/academy/controllers.py Tue Aug 12 11:21:05 2014 +0200
|
||||
+++ b/academy/controllers.py Tue Aug 12 11:24:14 2014 +0200
|
||||
@@ -4,7 +4,7 @@ from openerp import http
|
||||
# Parent 4c1c3d77ba8ad35dc759405a0db39a6eae943b0f
|
||||
|
||||
diff --git a/academy/controllers.py b/academy/controllers.py
|
||||
--- a/academy/controllers.py
|
||||
+++ b/academy/controllers.py
|
||||
@@ -4,8 +4,9 @@ from openerp import http
|
||||
class Academy(http.Controller):
|
||||
@http.route('/academy/', auth='public')
|
||||
def index(self):
|
||||
@http.route('/academy/academy/', auth='public')
|
||||
def index(self, **kw):
|
||||
+ Teachers = http.request.env['academy.teachers']
|
||||
return http.request.render('academy.index', {
|
||||
- 'teachers': ["Diana Padilla", "Jody Caroll", "Lester Vaughn"],
|
||||
+ 'teachers': Teachers.search([]),
|
||||
+ 'teachers': Teachers.search([])
|
||||
})
|
||||
-
|
||||
diff -r f0a3629abb17 -r 7ae0db4c2ddf academy/templates.xml
|
||||
--- a/academy/templates.xml Tue Aug 12 11:21:05 2014 +0200
|
||||
+++ b/academy/templates.xml Tue Aug 12 11:24:14 2014 +0200
|
||||
@@ -2,7 +2,7 @@
|
||||
<template id="index">
|
||||
<title>Academy</title>
|
||||
<t t-foreach="teachers" t-as="teacher">
|
||||
- <p><t t-esc="teacher"/></p>
|
||||
+ <p><t t-esc="teacher.id"/> <t t-esc="teacher.name"/></p>
|
||||
</t>
|
||||
</template>
|
||||
-</data></openerp>
|
||||
\ No newline at end of file
|
||||
+</data></openerp>
|
||||
|
||||
# @http.route('/academy/academy/objects/', auth='public')
|
||||
diff --git a/academy/templates.xml b/academy/templates.xml
|
||||
--- a/academy/templates.xml
|
||||
+++ b/academy/templates.xml
|
||||
@@ -3,7 +3,7 @@
|
||||
<template id="index">
|
||||
<title>Academy</title>
|
||||
<t t-foreach="teachers" t-as="teacher">
|
||||
- <p><t t-esc="teacher"/></p>
|
||||
+ <p><t t-esc="teacher.id"/> <t t-esc="teacher.name"/></p>
|
||||
</t>
|
||||
</template>
|
||||
<!-- <template id="object"> -->
|
||||
|
||||
@@ -1,29 +1,42 @@
|
||||
# HG changeset patch
|
||||
# Parent c77aa2c3341b9ab3a4e9ed05874e8a278fe69453
|
||||
diff -r c77aa2c3341b -r f0a3629abb17 academy/__openerp__.py
|
||||
--- a/academy/__openerp__.py Mon Aug 11 16:50:01 2014 +0200
|
||||
+++ b/academy/__openerp__.py Tue Aug 12 11:21:05 2014 +0200
|
||||
@@ -11,4 +11,8 @@
|
||||
'ir.model.access.csv',
|
||||
'templates.xml',
|
||||
],
|
||||
+ # data files which are only installed in "demonstration mode"
|
||||
+ 'demo': [
|
||||
+ 'demo.xml',
|
||||
+ ],
|
||||
}
|
||||
diff -r c77aa2c3341b -r f0a3629abb17 academy/demo.xml
|
||||
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
|
||||
+++ b/academy/demo.xml Tue Aug 12 11:21:05 2014 +0200
|
||||
@@ -0,0 +1,11 @@
|
||||
+<openerp><data>
|
||||
+ <record id="padilla" model="academy.teachers">
|
||||
+ <field name="name">Diana Padilla</field>
|
||||
+ </record>
|
||||
+ <record id="carroll" model="academy.teachers">
|
||||
+ <field name="name">Jody Carroll</field>
|
||||
+ </record>
|
||||
+ <record id="vaughn" model="academy.teachers">
|
||||
+ <field name="name">Lester Vaughn</field>
|
||||
+ </record>
|
||||
+</data></openerp>
|
||||
# Parent f416ab6c60f9cb3a803d30ff4dbff880eaa44a65
|
||||
|
||||
diff --git a/academy/demo.xml b/academy/demo.xml
|
||||
--- a/academy/demo.xml
|
||||
+++ b/academy/demo.xml
|
||||
@@ -1,25 +1,13 @@
|
||||
<openerp>
|
||||
<data>
|
||||
- <!-- -->
|
||||
- <!-- <record id="object0" model="academy.academy"> -->
|
||||
- <!-- <field name="name">Object 0</field> -->
|
||||
- <!-- </record> -->
|
||||
- <!-- -->
|
||||
- <!-- <record id="object1" model="academy.academy"> -->
|
||||
- <!-- <field name="name">Object 1</field> -->
|
||||
- <!-- </record> -->
|
||||
- <!-- -->
|
||||
- <!-- <record id="object2" model="academy.academy"> -->
|
||||
- <!-- <field name="name">Object 2</field> -->
|
||||
- <!-- </record> -->
|
||||
- <!-- -->
|
||||
- <!-- <record id="object3" model="academy.academy"> -->
|
||||
- <!-- <field name="name">Object 3</field> -->
|
||||
- <!-- </record> -->
|
||||
- <!-- -->
|
||||
- <!-- <record id="object4" model="academy.academy"> -->
|
||||
- <!-- <field name="name">Object 4</field> -->
|
||||
- <!-- </record> -->
|
||||
- <!-- -->
|
||||
+ <record id="padilla" model="academy.teachers">
|
||||
+ <field name="name">Diana Padilla</field>
|
||||
+ </record>
|
||||
+ <record id="carroll" model="academy.teachers">
|
||||
+ <field name="name">Jody Carroll</field>
|
||||
+ </record>
|
||||
+ <record id="vaughn" model="academy.teachers">
|
||||
+ <field name="name">Lester Vaughn</field>
|
||||
+ </record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
# HG changeset patch
|
||||
# Parent f3ad3f919b49bc70c87e1500ec0d39f741243b3d
|
||||
diff -r f3ad3f919b49 -r c3d04f578b76 academy/templates.xml
|
||||
--- a/academy/templates.xml Tue Aug 12 17:30:53 2014 +0200
|
||||
+++ b/academy/templates.xml Tue Aug 12 17:34:31 2014 +0200
|
||||
# Parent 07a68419287382d7dd9d600c8b813c3c37a88864
|
||||
|
||||
diff --git a/academy/templates.xml b/academy/templates.xml
|
||||
--- a/academy/templates.xml
|
||||
+++ b/academy/templates.xml
|
||||
@@ -21,7 +21,7 @@
|
||||
<div class="oe_structure">
|
||||
<div class="container">
|
||||
<h3 t-field="person.name"/>
|
||||
- <p>Last modified: <i t-field="person.write_date"/></p>
|
||||
+ <p>Last modified: <i t-field="person.write_date" t-field-options='{"format": "long"}'/></p>
|
||||
<div t-field="person.biography"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_structure">
|
||||
<div class="container">
|
||||
<h3 t-field="person.name"/>
|
||||
- <p>Last modified: <i t-field="person.write_date"/></p>
|
||||
+ <p>Last modified: <i t-field="person.write_date" t-field-options='{"format": "long"}'/></p>
|
||||
<div t-field="person.biography"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
# HG changeset patch
|
||||
# Parent d5b8a2de3e35c26c29f85ce500d41ccd2373c508
|
||||
diff -r d5b8a2de3e35 -r f3ad3f919b49 academy/templates.xml
|
||||
--- a/academy/templates.xml Tue Aug 12 17:25:15 2014 +0200
|
||||
+++ b/academy/templates.xml Tue Aug 12 17:30:53 2014 +0200
|
||||
# Parent 65d27383542ef9214ff9e2c308aa54b042cff2d9
|
||||
|
||||
diff --git a/academy/templates.xml b/academy/templates.xml
|
||||
--- a/academy/templates.xml
|
||||
+++ b/academy/templates.xml
|
||||
@@ -21,6 +21,7 @@
|
||||
<div class="oe_structure">
|
||||
<div class="container">
|
||||
<h3 t-field="person.name"/>
|
||||
+ <p>Last modified: <i t-field="person.write_date"/></p>
|
||||
<div t-field="person.biography"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_structure">
|
||||
<div class="container">
|
||||
<h3 t-field="person.name"/>
|
||||
+ <p>Last modified: <i t-field="person.write_date"/></p>
|
||||
<div t-field="person.biography"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
# HG changeset patch
|
||||
# Parent c3d04f578b762612422e09777e0ac5b3863d6ea6
|
||||
diff -r c3d04f578b76 -r c8cc9e66a270 academy/templates.xml
|
||||
--- a/academy/templates.xml Tue Aug 12 17:34:31 2014 +0200
|
||||
+++ b/academy/templates.xml Wed Aug 13 10:11:49 2014 +0200
|
||||
# Parent 0bc39fe6579c4e48b61e77bc970da2e1784f1a67
|
||||
|
||||
diff --git a/academy/templates.xml b/academy/templates.xml
|
||||
--- a/academy/templates.xml
|
||||
+++ b/academy/templates.xml
|
||||
@@ -21,7 +21,7 @@
|
||||
<div class="oe_structure">
|
||||
<div class="container">
|
||||
<h3 t-field="person.name"/>
|
||||
- <p>Last modified: <i t-field="person.write_date" t-field-options='{"format": "long"}'/></p>
|
||||
+ <p>Last modified: <i t-field="person.write_date" t-field-options='{"widget": "relative"}'/></p>
|
||||
<div t-field="person.biography"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_structure">
|
||||
<div class="container">
|
||||
<h3 t-field="person.name"/>
|
||||
- <p>Last modified: <i t-field="person.write_date" t-field-options='{"format": "long"}'/></p>
|
||||
+ <p>Last modified: <i t-field="person.write_date" t-field-options='{"widget": "relative"}'/></p>
|
||||
<div t-field="person.biography"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,19 +1,153 @@
|
||||
# HG changeset patch
|
||||
# Parent 0000000000000000000000000000000000000000
|
||||
# Parent 0000000000000000000000000000000000000000
|
||||
|
||||
diff --git a/academy/__init__.py b/academy/__init__.py
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/academy/__init__.py
|
||||
@@ -0,0 +1,3 @@
|
||||
+# -*- coding: utf-8 -*-
|
||||
+import controllers
|
||||
+import models
|
||||
diff --git a/academy/__openerp__.py b/academy/__openerp__.py
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/academy/__openerp__.py
|
||||
@@ -0,0 +1,9 @@
|
||||
@@ -0,0 +1,34 @@
|
||||
+# -*- coding: utf-8 -*-
|
||||
+{
|
||||
+ # The human-readable name of your module, displayed in the interface
|
||||
+ 'name': "Academy",
|
||||
+ # A more extensive description
|
||||
+
|
||||
+ 'summary': """
|
||||
+ Short (1 phrase/line) summary of the module's purpose, used as
|
||||
+ subtitle on modules listing or apps.openerp.com""",
|
||||
+
|
||||
+ 'description': """
|
||||
+ Long description of module's purpose
|
||||
+ """,
|
||||
+ # Which modules must be installed for this one to work
|
||||
+
|
||||
+ 'author': "Your Company",
|
||||
+ 'website': "http://www.yourcompany.com",
|
||||
+
|
||||
+ # Categories can be used to filter modules in modules listing
|
||||
+ # Check https://github.com/odoo/odoo/blob/master/openerp/addons/base/module/module_data.xml
|
||||
+ # for the full list
|
||||
+ 'category': 'Uncategorized',
|
||||
+ 'version': '0.1',
|
||||
+
|
||||
+ # any module necessary for this one to work correctly
|
||||
+ 'depends': ['base'],
|
||||
+
|
||||
+ # always loaded
|
||||
+ 'data': [
|
||||
+ # 'security/ir.model.access.csv',
|
||||
+ 'templates.xml',
|
||||
+ ],
|
||||
+ # only loaded in demonstration mode
|
||||
+ 'demo': [
|
||||
+ 'demo.xml',
|
||||
+ ],
|
||||
+}
|
||||
diff --git a/academy/controllers.py b/academy/controllers.py
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/academy/controllers.py
|
||||
@@ -0,0 +1,20 @@
|
||||
+# -*- coding: utf-8 -*-
|
||||
+from openerp import http
|
||||
+
|
||||
+# class Academy(http.Controller):
|
||||
+# @http.route('/academy/academy/', auth='public')
|
||||
+# def index(self, **kw):
|
||||
+# return "Hello, world"
|
||||
+
|
||||
+# @http.route('/academy/academy/objects/', auth='public')
|
||||
+# def list(self, **kw):
|
||||
+# return http.request.render('academy.listing', {
|
||||
+# 'root': '/academy/academy',
|
||||
+# 'objects': http.request.env['academy.academy'].search([]),
|
||||
+# })
|
||||
+
|
||||
+# @http.route('/academy/academy/objects/<model("academy.academy"):obj>/', auth='public')
|
||||
+# def object(self, obj, **kw):
|
||||
+# return http.request.render('academy.object', {
|
||||
+# 'object': obj
|
||||
+# })
|
||||
diff --git a/academy/demo.xml b/academy/demo.xml
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/academy/demo.xml
|
||||
@@ -0,0 +1,25 @@
|
||||
+<openerp>
|
||||
+ <data>
|
||||
+ <!-- -->
|
||||
+ <!-- <record id="object0" model="academy.academy"> -->
|
||||
+ <!-- <field name="name">Object 0</field> -->
|
||||
+ <!-- </record> -->
|
||||
+ <!-- -->
|
||||
+ <!-- <record id="object1" model="academy.academy"> -->
|
||||
+ <!-- <field name="name">Object 1</field> -->
|
||||
+ <!-- </record> -->
|
||||
+ <!-- -->
|
||||
+ <!-- <record id="object2" model="academy.academy"> -->
|
||||
+ <!-- <field name="name">Object 2</field> -->
|
||||
+ <!-- </record> -->
|
||||
+ <!-- -->
|
||||
+ <!-- <record id="object3" model="academy.academy"> -->
|
||||
+ <!-- <field name="name">Object 3</field> -->
|
||||
+ <!-- </record> -->
|
||||
+ <!-- -->
|
||||
+ <!-- <record id="object4" model="academy.academy"> -->
|
||||
+ <!-- <field name="name">Object 4</field> -->
|
||||
+ <!-- </record> -->
|
||||
+ <!-- -->
|
||||
+ </data>
|
||||
+</openerp>
|
||||
diff --git a/academy/models.py b/academy/models.py
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/academy/models.py
|
||||
@@ -0,0 +1,8 @@
|
||||
+# -*- coding: utf-8 -*-
|
||||
+
|
||||
+from openerp import models, fields, api
|
||||
+
|
||||
+# class academy(models.Model):
|
||||
+# _name = 'academy.academy'
|
||||
+
|
||||
+# name = fields.Char()
|
||||
diff --git a/academy/security/ir.model.access.csv b/academy/security/ir.model.access.csv
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/academy/security/ir.model.access.csv
|
||||
@@ -0,0 +1,2 @@
|
||||
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
+access_academy_academy,academy.academy,model_academy_academy,,1,0,0,0
|
||||
diff --git a/academy/templates.xml b/academy/templates.xml
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/academy/templates.xml
|
||||
@@ -0,0 +1,22 @@
|
||||
+<openerp>
|
||||
+ <data>
|
||||
+ <!-- <template id="listing"> -->
|
||||
+ <!-- <ul> -->
|
||||
+ <!-- <li t-foreach="objects" t-as="object"> -->
|
||||
+ <!-- <a t-attf-href="#{ root }/objects/#{ object.id }"> -->
|
||||
+ <!-- <t t-esc="object.display_name"/> -->
|
||||
+ <!-- </a> -->
|
||||
+ <!-- </li> -->
|
||||
+ <!-- </ul> -->
|
||||
+ <!-- </template> -->
|
||||
+ <!-- <template id="object"> -->
|
||||
+ <!-- <h1><t t-esc="object.display_name"/></h1> -->
|
||||
+ <!-- <dl> -->
|
||||
+ <!-- <t t-foreach="object._fields" t-as="field"> -->
|
||||
+ <!-- <dt><t t-esc="field"/></dt> -->
|
||||
+ <!-- <dd><t t-esc="object[field]"/></dd> -->
|
||||
+ <!-- </t> -->
|
||||
+ <!-- </dl> -->
|
||||
+ <!-- </template> -->
|
||||
+ </data>
|
||||
+</openerp>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
# HG changeset patch
|
||||
# Parent 6106208621f01d5aea67727f09adf5c5b2a38642
|
||||
diff -r 6106208621f0 -r f882c3b2e7af academy/__openerp__.py
|
||||
--- a/academy/__openerp__.py Wed Aug 13 16:32:26 2014 +0200
|
||||
+++ b/academy/__openerp__.py Thu Aug 14 11:44:51 2014 +0200
|
||||
@@ -5,7 +5,7 @@
|
||||
'description': """
|
||||
""",
|
||||
# Which modules must be installed for this one to work
|
||||
# Parent ae97cfb61fd0e1a47727a9d8ed4a702116254eff
|
||||
|
||||
diff --git a/academy/__openerp__.py b/academy/__openerp__.py
|
||||
--- a/academy/__openerp__.py
|
||||
+++ b/academy/__openerp__.py
|
||||
@@ -20,7 +20,7 @@
|
||||
'version': '0.1',
|
||||
|
||||
# any module necessary for this one to work correctly
|
||||
- 'depends': ['website'],
|
||||
+ 'depends': ['website_sale'],
|
||||
# data files which are always installed
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
'ir.model.access.csv',
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
# HG changeset patch
|
||||
# Parent 0795247fa6cbb63238941f3dd26a8b6144fa71ee
|
||||
diff -r 0795247fa6cb -r e9d9740fa185 academy/controllers.py
|
||||
--- a/academy/controllers.py Tue Aug 12 11:27:14 2014 +0200
|
||||
+++ b/academy/controllers.py Tue Aug 12 16:51:29 2014 +0200
|
||||
@@ -8,3 +8,8 @@ class Academy(http.Controller):
|
||||
return http.request.render('academy.index', {
|
||||
'teachers': Teachers.search([]),
|
||||
# Parent f66ff336f8ed20e022660bd74a43af1b44a1f275
|
||||
|
||||
diff --git a/academy/controllers.py b/academy/controllers.py
|
||||
--- a/academy/controllers.py
|
||||
+++ b/academy/controllers.py
|
||||
@@ -9,6 +9,11 @@ class Academy(http.Controller):
|
||||
'teachers': Teachers.search([])
|
||||
})
|
||||
+
|
||||
|
||||
+ @http.route('/academy/<name>/', auth='public', website=True)
|
||||
+ def teacher(self, name):
|
||||
+ return '<h1>{}</h1>'.format(name)
|
||||
+
|
||||
+
|
||||
# @http.route('/academy/academy/objects/', auth='public')
|
||||
# def list(self, **kw):
|
||||
# return http.request.render('academy.listing', {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
# HG changeset patch
|
||||
# Parent e9d9740fa1852c80541c6b5b94280c8ec74cc4bb
|
||||
diff -r e9d9740fa185 -r 122340f43602 academy/controllers.py
|
||||
--- a/academy/controllers.py Tue Aug 12 16:51:29 2014 +0200
|
||||
+++ b/academy/controllers.py Tue Aug 12 16:52:38 2014 +0200
|
||||
@@ -9,7 +9,7 @@ class Academy(http.Controller):
|
||||
'teachers': Teachers.search([]),
|
||||
# Parent cf501446e2f8d7a059ef7bd16866ed03c8f2a04f
|
||||
|
||||
diff --git a/academy/controllers.py b/academy/controllers.py
|
||||
--- a/academy/controllers.py
|
||||
+++ b/academy/controllers.py
|
||||
@@ -9,9 +9,9 @@ class Academy(http.Controller):
|
||||
'teachers': Teachers.search([])
|
||||
})
|
||||
|
||||
- @http.route('/academy/<name>/', auth='public', website=True)
|
||||
@@ -14,3 +16,5 @@ diff -r e9d9740fa185 -r 122340f43602 academy/controllers.py
|
||||
+ def teacher(self, id):
|
||||
+ return '<h1>{} ({})</h1>'.format(id, type(id).__name__)
|
||||
|
||||
|
||||
# @http.route('/academy/academy/objects/', auth='public')
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
# HG changeset patch
|
||||
# Parent 91ffd360f3ffd44acb605c09753498b3d5f3f210
|
||||
# Parent 976a92eff9104d355cb86a0b41e883a19e75e923
|
||||
|
||||
diff --git a/academy/templates.xml b/academy/templates.xml
|
||||
--- a/academy/templates.xml
|
||||
+++ b/academy/templates.xml
|
||||
@@ -5,7 +5,9 @@
|
||||
<div class="oe_structure">
|
||||
<div class="container">
|
||||
<t t-foreach="teachers" t-as="teacher">
|
||||
- <p><t t-esc="teacher.id"/> <t t-esc="teacher.name"/></p>
|
||||
+ <p><a t-attf-href="/academy/{{ slug(teacher) }}">
|
||||
+ <t t-esc="teacher.name"/></a>
|
||||
+ </p>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
@@ -6,7 +6,9 @@
|
||||
<div class="oe_structure">
|
||||
<div class="container">
|
||||
<t t-foreach="teachers" t-as="teacher">
|
||||
- <p><t t-esc="teacher.id"/> <t t-esc="teacher.name"/></p>
|
||||
+ <p><a t-attf-href="/academy/{{ slug(teacher) }}">
|
||||
+ <t t-esc="teacher.name"/></a>
|
||||
+ </p>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
@@ -18,7 +20,7 @@
|
||||
<div class="oe_structure"/>
|
||||
<div class="oe_structure">
|
||||
<div class="container">
|
||||
- <p><t t-esc="person.id"/> <t t-esc="person.name"/></p>
|
||||
+ <h3><t t-esc="person.name"/></h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_structure"/>
|
||||
<div class="oe_structure"/>
|
||||
<div class="oe_structure">
|
||||
<div class="container">
|
||||
- <p><t t-esc="person.id"/> <t t-esc="person.name"/></p>
|
||||
+ <h3><t t-esc="person.name"/></h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_structure"/>
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
# HG changeset patch
|
||||
# Parent c8cc9e66a2701dad6589ff1d950e1cb7738d0854
|
||||
diff -r c8cc9e66a270 -r 0201053e995d academy/__openerp__.py
|
||||
--- a/academy/__openerp__.py Wed Aug 13 10:11:49 2014 +0200
|
||||
+++ b/academy/__openerp__.py Wed Aug 13 11:04:49 2014 +0200
|
||||
@@ -10,6 +10,7 @@
|
||||
# Parent 44a09b8865d418cda8b7c46dfb70fadc86e22184
|
||||
|
||||
diff --git a/academy/__openerp__.py b/academy/__openerp__.py
|
||||
--- a/academy/__openerp__.py
|
||||
+++ b/academy/__openerp__.py
|
||||
@@ -26,6 +26,7 @@
|
||||
'data': [
|
||||
'ir.model.access.csv',
|
||||
'security/ir.model.access.csv',
|
||||
'templates.xml',
|
||||
+ 'views.xml',
|
||||
],
|
||||
# data files which are only installed in "demonstration mode"
|
||||
# only loaded in demonstration mode
|
||||
'demo': [
|
||||
diff -r c8cc9e66a270 -r 0201053e995d academy/views.xml
|
||||
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
|
||||
+++ b/academy/views.xml Wed Aug 13 11:04:49 2014 +0200
|
||||
diff --git a/academy/views.xml b/academy/views.xml
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/academy/views.xml
|
||||
@@ -0,0 +1,13 @@
|
||||
+<openerp><data>
|
||||
+ <record id="action_academy_teachers" model="ir.actions.act_window">
|
||||
|
||||
+35
-32
@@ -1,39 +1,42 @@
|
||||
# HG changeset patch
|
||||
# Parent d5ad2ad84a5db0668e4e08fefb6e3f628c2e14d1
|
||||
diff -r d5ad2ad84a5d -r 3e800beb7db0 academy/__openerp__.py
|
||||
--- a/academy/__openerp__.py Mon Aug 11 16:12:38 2014 +0200
|
||||
+++ b/academy/__openerp__.py Mon Aug 11 16:36:15 2014 +0200
|
||||
@@ -6,4 +6,8 @@
|
||||
""",
|
||||
# Which modules must be installed for this one to work
|
||||
'depends': ['base'],
|
||||
+ # data files which are always installed
|
||||
+ 'data': [
|
||||
+ 'templates.xml',
|
||||
+ ],
|
||||
}
|
||||
diff -r d5ad2ad84a5d -r 3e800beb7db0 academy/controllers.py
|
||||
--- a/academy/controllers.py Mon Aug 11 16:12:38 2014 +0200
|
||||
+++ b/academy/controllers.py Mon Aug 11 16:36:15 2014 +0200
|
||||
@@ -4,5 +4,7 @@ from openerp import http
|
||||
# Parent e65c9826721cb2421131399bc9223e03b482d38f
|
||||
|
||||
diff --git a/academy/controllers.py b/academy/controllers.py
|
||||
--- a/academy/controllers.py
|
||||
+++ b/academy/controllers.py
|
||||
@@ -4,7 +4,9 @@ from openerp import http
|
||||
class Academy(http.Controller):
|
||||
@http.route('/academy/', auth='public')
|
||||
def index(self):
|
||||
- return "Hello, world!"
|
||||
@http.route('/academy/academy/', auth='public')
|
||||
def index(self, **kw):
|
||||
- return "Hello, world"
|
||||
+ return http.request.render('academy.index', {
|
||||
+ 'teachers': ["Diana Padilla", "Jody Caroll", "Lester Vaughn"],
|
||||
+ })
|
||||
|
||||
diff -r d5ad2ad84a5d -r 3e800beb7db0 academy/templates.xml
|
||||
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
|
||||
+++ b/academy/templates.xml Mon Aug 11 16:36:15 2014 +0200
|
||||
@@ -0,0 +1,8 @@
|
||||
+<openerp><data>
|
||||
+ <template id="index">
|
||||
+ <title>Academy</title>
|
||||
+ <t t-foreach="teachers" t-as="teacher">
|
||||
+ <p><t t-esc="teacher"/></p>
|
||||
+ </t>
|
||||
+ </template>
|
||||
+</data></openerp>
|
||||
\ No newline at end of file
|
||||
# @http.route('/academy/academy/objects/', auth='public')
|
||||
# def list(self, **kw):
|
||||
diff --git a/academy/templates.xml b/academy/templates.xml
|
||||
--- a/academy/templates.xml
|
||||
+++ b/academy/templates.xml
|
||||
@@ -1,14 +1,11 @@
|
||||
<openerp>
|
||||
<data>
|
||||
- <!-- <template id="listing"> -->
|
||||
- <!-- <ul> -->
|
||||
- <!-- <li t-foreach="objects" t-as="object"> -->
|
||||
- <!-- <a t-attf-href="#{ root }/objects/#{ object.id }"> -->
|
||||
- <!-- <t t-esc="object.display_name"/> -->
|
||||
- <!-- </a> -->
|
||||
- <!-- </li> -->
|
||||
- <!-- </ul> -->
|
||||
- <!-- </template> -->
|
||||
+ <template id="index">
|
||||
+ <title>Academy</title>
|
||||
+ <t t-foreach="teachers" t-as="teacher">
|
||||
+ <p><t t-esc="teacher"/></p>
|
||||
+ </t>
|
||||
+ </template>
|
||||
<!-- <template id="object"> -->
|
||||
<!-- <h1><t t-esc="object.display_name"/></h1> -->
|
||||
<!-- <dl> -->
|
||||
|
||||
@@ -1,47 +1,50 @@
|
||||
# HG changeset patch
|
||||
# Parent 7ae0db4c2ddf5d0f7f58db2af8976fcec905cc4e
|
||||
diff -r 7ae0db4c2ddf -r 0795247fa6cb academy/__openerp__.py
|
||||
--- a/academy/__openerp__.py Tue Aug 12 11:24:14 2014 +0200
|
||||
+++ b/academy/__openerp__.py Tue Aug 12 11:27:14 2014 +0200
|
||||
@@ -5,7 +5,7 @@
|
||||
'description': """
|
||||
""",
|
||||
# Which modules must be installed for this one to work
|
||||
# Parent 06f959831b14dd5d2e0a57d3e14bf919e18f8e3a
|
||||
|
||||
diff --git a/academy/__openerp__.py b/academy/__openerp__.py
|
||||
--- a/academy/__openerp__.py
|
||||
+++ b/academy/__openerp__.py
|
||||
@@ -20,7 +20,7 @@
|
||||
'version': '0.1',
|
||||
|
||||
# any module necessary for this one to work correctly
|
||||
- 'depends': ['base'],
|
||||
+ 'depends': ['website'],
|
||||
# data files which are always installed
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
'ir.model.access.csv',
|
||||
diff -r 7ae0db4c2ddf -r 0795247fa6cb academy/controllers.py
|
||||
--- a/academy/controllers.py Tue Aug 12 11:24:14 2014 +0200
|
||||
+++ b/academy/controllers.py Tue Aug 12 11:27:14 2014 +0200
|
||||
diff --git a/academy/controllers.py b/academy/controllers.py
|
||||
--- a/academy/controllers.py
|
||||
+++ b/academy/controllers.py
|
||||
@@ -2,7 +2,7 @@
|
||||
from openerp import http
|
||||
|
||||
class Academy(http.Controller):
|
||||
- @http.route('/academy/', auth='public')
|
||||
+ @http.route('/academy/', auth='public', website=True)
|
||||
def index(self):
|
||||
- @http.route('/academy/academy/', auth='public')
|
||||
+ @http.route('/academy/academy/', auth='public', website=True)
|
||||
def index(self, **kw):
|
||||
Teachers = http.request.env['academy.teachers']
|
||||
return http.request.render('academy.index', {
|
||||
diff -r 7ae0db4c2ddf -r 0795247fa6cb academy/templates.xml
|
||||
--- a/academy/templates.xml Tue Aug 12 11:24:14 2014 +0200
|
||||
+++ b/academy/templates.xml Tue Aug 12 11:27:14 2014 +0200
|
||||
@@ -1,8 +1,14 @@
|
||||
<openerp><data>
|
||||
<template id="index">
|
||||
- <title>Academy</title>
|
||||
- <t t-foreach="teachers" t-as="teacher">
|
||||
- <p><t t-esc="teacher.id"/> <t t-esc="teacher.name"/></p>
|
||||
+ <t t-call="website.layout">
|
||||
+ <t t-set="title">Academy</t>
|
||||
+ <div class="oe_structure">
|
||||
+ <div class="container">
|
||||
+ <t t-foreach="teachers" t-as="teacher">
|
||||
+ <p><t t-esc="teacher.id"/> <t t-esc="teacher.name"/></p>
|
||||
+ </t>
|
||||
+ </div>
|
||||
+ </div>
|
||||
</t>
|
||||
</template>
|
||||
</data></openerp>
|
||||
diff --git a/academy/templates.xml b/academy/templates.xml
|
||||
--- a/academy/templates.xml
|
||||
+++ b/academy/templates.xml
|
||||
@@ -1,9 +1,15 @@
|
||||
<openerp>
|
||||
<data>
|
||||
<template id="index">
|
||||
- <title>Academy</title>
|
||||
- <t t-foreach="teachers" t-as="teacher">
|
||||
- <p><t t-esc="teacher.id"/> <t t-esc="teacher.name"/></p>
|
||||
+ <t t-call="website.layout">
|
||||
+ <t t-set="title">Academy</t>
|
||||
+ <div class="oe_structure">
|
||||
+ <div class="container">
|
||||
+ <t t-foreach="teachers" t-as="teacher">
|
||||
+ <p><t t-esc="teacher.id"/> <t t-esc="teacher.name"/></p>
|
||||
+ </t>
|
||||
+ </div>
|
||||
+ </div>
|
||||
</t>
|
||||
</template>
|
||||
<!-- <template id="object"> -->
|
||||
|
||||
@@ -370,10 +370,36 @@ server actions:
|
||||
|
||||
.. _reference/actions/report:
|
||||
|
||||
Report Actions
|
||||
==============
|
||||
Report Actions (``ir.actions.report.xml``)
|
||||
==========================================
|
||||
|
||||
.. todo:: sle-odoo
|
||||
Triggers the printing of a report
|
||||
|
||||
``name`` (mandatory)
|
||||
only useful as a mnemonic/description of the report when looking for one
|
||||
in a list of some sort
|
||||
``model`` (mandatory)
|
||||
the model your report will be about
|
||||
``report_type`` (mandatory)
|
||||
either ``qweb-pdf`` for PDF reports or ``qweb-html`` for HTML
|
||||
``report_name``
|
||||
the name of your report (which will be the name of the PDF output)
|
||||
``groups_id``
|
||||
:class:`~openerp.fields.Many2many` field to the groups allowed to view/use
|
||||
the current report
|
||||
``paperformat_id``
|
||||
:class:`~openerp.fields.Many2one` field to the paper format you wish to
|
||||
use for this report (if not specified, the company format will be used)
|
||||
``attachment_use``
|
||||
if set to ``True``, the report is only generated once the first time it is
|
||||
requested, and re-printed from the stored report afterwards instead of
|
||||
being re-generated every time.
|
||||
|
||||
Can be used for reports which must only be generated once (e.g. for legal
|
||||
reasons)
|
||||
``attachment``
|
||||
python expression that defines the name of the report; the record is
|
||||
accessible as the variable ``object``
|
||||
|
||||
.. _reference/actions/client:
|
||||
|
||||
|
||||
+266
-31
@@ -1,37 +1,126 @@
|
||||
.. _reference/reports:
|
||||
.. highlight:: xml
|
||||
|
||||
============
|
||||
QWeb Reports
|
||||
============
|
||||
|
||||
Paper Formats
|
||||
-------------
|
||||
Reports are written in HTML/QWeb, like all regular views in Odoo. You can use
|
||||
the usual :ref:`QWeb control flow tools <reference/qweb>`. The PDF rendering
|
||||
itself is performed by wkhtmltopdf_.
|
||||
|
||||
There is a model called Paper Format allowing to define details specific to
|
||||
the PDF output. These details include margins, header line, ... Everything
|
||||
related to the printed pdf. Defining a paper format is not mandatory as there
|
||||
is a default one set on the company. If you want a specific report to be
|
||||
associated to a specific paper format , just link the ir.actions.report.xml to
|
||||
it.
|
||||
If you want to create a report on a certain model, you will need to define
|
||||
this :ref:`reference/reports/report` and the
|
||||
:ref:`reference/reports/templates` it will use. If you wish, you can also
|
||||
specify a specific :ref:`reference/reports/paper_formats` for this
|
||||
report. Finally, if you need access to more than your model, you can define a
|
||||
:ref:`reference/reports/custom_reports` class that gives you access to more
|
||||
models and records in the template.
|
||||
|
||||
Expressions used in Odoo report templates
|
||||
-----------------------------------------
|
||||
.. _reference/reports/report:
|
||||
|
||||
There are some magic variables used in the report rendering. The main ones are
|
||||
the following:
|
||||
Report
|
||||
======
|
||||
|
||||
Every report must be declared by a :ref:`report action
|
||||
<reference/actions/report>`.
|
||||
|
||||
For simplicity, a shortcut ``<report>`` element is available to define a
|
||||
report, rather than have to set up :ref:`the action
|
||||
<reference/actions/report>` and its surroundings manually. That ``<report>``
|
||||
can take the following attributes:
|
||||
|
||||
``id``
|
||||
the generated record's :term:`external id`
|
||||
``name`` (mandatory)
|
||||
only useful as a mnemonic/description of the report when looking for one
|
||||
in a list of some sort
|
||||
``model`` (mandatory)
|
||||
the model your report will be about
|
||||
``report_type`` (mandatory)
|
||||
either ``qweb-pdf`` for PDF reports or ``qweb-html`` for HTML
|
||||
``report_name``
|
||||
the name of your report (which will be the name of the PDF output)
|
||||
``groups``
|
||||
:class:`~openerp.fields.Many2many` field to the groups allowed to view/use
|
||||
the current report
|
||||
``attachment_use``
|
||||
if set to True, the report will be stored as an attachment of the record
|
||||
using the name generated by the ``attachment`` expression; you can use
|
||||
this if you need your report to be generated only once (for legal reasons,
|
||||
for example)
|
||||
``attachment``
|
||||
python expression that defines the name of the report; the record is
|
||||
acessible as the variable ``object``
|
||||
|
||||
.. warning::
|
||||
|
||||
The paper format cannot currently be declared via the ``<report>``
|
||||
shortcut, it must be added afterwards using a ``<record>`` extension on the
|
||||
report action itself::
|
||||
|
||||
<record id="<report_id>" model="ir.actions.report.xml">
|
||||
<field name="paperformat_id" ref="<paperformat>"/>
|
||||
</record>
|
||||
|
||||
Example::
|
||||
|
||||
<report
|
||||
id="account_invoices"
|
||||
model="account.invoice"
|
||||
string="Invoices"
|
||||
report_type="qweb-pdf"
|
||||
name="account.report_invoice"
|
||||
file="account.report_invoice"
|
||||
attachment_use="True"
|
||||
attachment="(object.state in ('open','paid')) and
|
||||
('INV'+(object.number or '').replace('/','')+'.pdf')"
|
||||
/>
|
||||
|
||||
.. _reference/reports/templates:
|
||||
|
||||
Report template
|
||||
===============
|
||||
|
||||
|
||||
Minimal viable template
|
||||
-----------------------
|
||||
|
||||
A minimal template would look like::
|
||||
|
||||
<template id="report_invoice">
|
||||
<t t-call="report.html_container">
|
||||
<t t-foreach="docs" t-as="o">
|
||||
<t t-call="report.external_layout">
|
||||
<div class="page">
|
||||
<h2>Report title</h2>
|
||||
<p>This object's name is <span t-field="o.name"/></p>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
Calling ``external_layout`` will add the default header and footer on your
|
||||
report. The PDF body will be the content inside the ``<div
|
||||
class="page">``. The template's ``id`` must be the name specified in the
|
||||
report declaration; for example ``account.report_invoice`` for the above
|
||||
report. Since this is a QWeb template, you can access all the fields of the
|
||||
``docs`` objects received by the template.
|
||||
|
||||
There are some specific variables accessible in reports, mainly:
|
||||
|
||||
``docs``
|
||||
records for the current report
|
||||
``doc_ids``
|
||||
list of ids for the ``docs`` records
|
||||
``doc_model``
|
||||
model for teh ``docs`` records
|
||||
model for the ``docs`` records
|
||||
``time``
|
||||
a reference to time_ from the Python standard library
|
||||
a reference to :mod:`python:time` from the Python standard library
|
||||
``translate_doc``
|
||||
a function to translate a part of a report. It must be used as follow:
|
||||
|
||||
.. code-block:: xml
|
||||
::
|
||||
|
||||
<t t-foreach="doc_ids" t-as="doc_id">
|
||||
<t t-raw="translate_doc(doc_id, doc_model, 'partner_id.lang', account.report_invoice_document')"/>
|
||||
@@ -39,36 +128,182 @@ the following:
|
||||
``user``
|
||||
``res.user`` record for the user printing the report
|
||||
``res_company``
|
||||
record the current ``user``'s company
|
||||
record for the current ``user``'s company
|
||||
|
||||
Custom report
|
||||
-------------
|
||||
If you wish to access other records/models in the template, you will need
|
||||
:ref:`a custom report <reference/reports/custom_reports>`.
|
||||
|
||||
A generic report use the default rendering context, containing the magic
|
||||
variables as explained before. If you want a new rendering context containing
|
||||
anything you want to process your data Odoo AbstractModel, a custom module is
|
||||
needed. These reports are called "particular report".
|
||||
Translatable Templates
|
||||
----------------------
|
||||
|
||||
For a particular report, you have to write an Odoo Model containing a
|
||||
render_html method. Classically, this method returns a call to the original
|
||||
**QWeb render** with a **custom rendering context**.
|
||||
If you wish to translate reports (to the language of a partner, for example),
|
||||
you need to define two templates:
|
||||
|
||||
* The main report template
|
||||
* The translatable document
|
||||
|
||||
You can then call translate_doc from your main template to obtain the
|
||||
translated document. If you wish to see the details of the translation in the
|
||||
backend, you can go to :menuselection:`Settings --> Reports --> Report -->
|
||||
<report_name> --> Search associated QWeb views --> <translatable_document> -->
|
||||
Associated translations`.
|
||||
|
||||
For example, let's look at the Sale Order report from the Sale module::
|
||||
|
||||
<!-- Main template -->
|
||||
<template id="sale.report_saleorder">
|
||||
<t t-call="report.html_container">
|
||||
<t t-foreach="doc_ids" t-as="doc_id">
|
||||
<t t-raw="translate_doc(doc_id, doc_model, 'partner_id.lang', 'sale.report_saleorder_document')"/>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Translatable template -->
|
||||
<template id="report_saleorder_document">
|
||||
<t t-call="report.external_layout">
|
||||
<div class="page">
|
||||
<div class="oe_structure"/>
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
<strong t-if="o.partner_shipping_id == o.partner_invoice_id">Invoice and shipping address:</strong>
|
||||
<strong t-if="o.partner_shipping_id != o.partner_invoice_id">Invoice address:</strong>
|
||||
<div t-field="o.partner_invoice_id" t-field-options="{"no_marker": true}"/>
|
||||
<...>
|
||||
<div class="oe_structure"/>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
|
||||
The main template calls translate_doc with ``partner_id.lang`` as a parameter,
|
||||
which means it uses :ref:`a custom report model
|
||||
<reference/reports/custom_reports>` to access a ``res.partner`` record.
|
||||
|
||||
Barcodes
|
||||
--------
|
||||
|
||||
Barcodes are images returned by a controller and can easily be embedded in
|
||||
reports thanks to the QWeb syntax:
|
||||
|
||||
.. code-block:: html
|
||||
|
||||
<img t-att-src="'/report/barcode/QR/%s' % 'My text in qr code'"/>
|
||||
|
||||
More parameters can be passed as a query string
|
||||
|
||||
.. code-block:: html
|
||||
|
||||
<img t-att-src="'/report/barcode/?
|
||||
type=%s&value=%s&width=%s&height=%s'%('QR', 'text', 200, 200)"/>
|
||||
|
||||
|
||||
Useful Remarks
|
||||
--------------
|
||||
* Twitter Bootstrap and FontAwesome classes can be used in your report
|
||||
template
|
||||
* Local CSS can be put directly in the template
|
||||
|
||||
* Global CSS can be inserted in the main report layout by inheriting its
|
||||
template and inserting your CSS::
|
||||
|
||||
<template id="report_saleorder_style" inherit_id="report.layout">
|
||||
<xpath expr="//style" position="after">
|
||||
<style type="text/css">
|
||||
.example-css-class {
|
||||
background-color: red;
|
||||
}
|
||||
</style>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
.. _reference/reports/paper_formats:
|
||||
|
||||
Paper Format
|
||||
============
|
||||
|
||||
Paper formats are records of ``report.paperformat`` and can contain the
|
||||
following attributes:
|
||||
|
||||
``name`` (mandatory)
|
||||
only useful as a mnemonic/description of the report when looking for one
|
||||
in a list of some sort
|
||||
``description``
|
||||
a small description of your format
|
||||
``format``
|
||||
either a predefined format (A0 to A9, B0 to B10, Legal, Letter,
|
||||
Tabloid,...) or ``custom``; A4 by default. You cannot use a non-custom
|
||||
format if you define the page dimensions.
|
||||
``dpi``
|
||||
output DPI; 90 by default
|
||||
``margin_top``, ``margin_bottom``, ``margin_left``, ``margin_right``
|
||||
margin sizes in mm
|
||||
``page_height``, ``page_width``
|
||||
page dimensions in mm
|
||||
``orientation``
|
||||
Landscape or Portrait
|
||||
``header_line``
|
||||
boolean to display a header line
|
||||
``header_spacing``
|
||||
header spacing in mm
|
||||
|
||||
Example::
|
||||
|
||||
<record id="paperformat_frenchcheck" model="report.paperformat">
|
||||
<field name="name">French Bank Check</field>
|
||||
<field name="default" eval="True"/>
|
||||
<field name="format">custom</field>
|
||||
<field name="page_height">80</field>
|
||||
<field name="page_width">175</field>
|
||||
<field name="orientation">Portrait</field>
|
||||
<field name="margin_top">3</field>
|
||||
<field name="margin_bottom">3</field>
|
||||
<field name="margin_left">3</field>
|
||||
<field name="margin_right">3</field>
|
||||
<field name="header_line" eval="False"/>
|
||||
<field name="header_spacing">3</field>
|
||||
<field name="dpi">80</field>
|
||||
</record>
|
||||
|
||||
.. _reference/reports/custom_reports:
|
||||
|
||||
Custom Reports
|
||||
==============
|
||||
|
||||
The report model has a default ``get_html`` function that looks for a model
|
||||
named :samp:`report.{module.report_name}`. If it exists, it will use it to
|
||||
call the QWeb engine; otherwise a generic function will be used. If you wish
|
||||
to customize your reports by including more things in the template (like
|
||||
records of others models, for example), you can define this model, overwrite
|
||||
the function ``render_html`` and pass objects in the ``docargs`` dictionnary:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from openerp import api, models
|
||||
|
||||
|
||||
class ParticularReport(models.AbstractModel):
|
||||
_name = 'report.<<module.reportname>>'
|
||||
_name = 'report.module.report_name'
|
||||
@api.multi
|
||||
def render_html(self, data=None):
|
||||
report_obj = self.env['report']
|
||||
report = report_obj._get_report_from_name('<<module.reportname>>')
|
||||
report = report_obj._get_report_from_name('module.report_name')
|
||||
docargs = {
|
||||
'doc_ids': self._ids,
|
||||
'doc_model': report.model,
|
||||
'docs': self,
|
||||
}
|
||||
return report_obj.render('<<module.reportname>>', docargs)
|
||||
return report_obj.render('module.report_name', docargs)
|
||||
|
||||
.. _time: https://docs.python.org/2/library/time.html
|
||||
Reports are web pages
|
||||
=====================
|
||||
|
||||
Reports are dynamically generated by the report module and can be accessed
|
||||
directly via URL:
|
||||
|
||||
For example, you can access a Sale Order report in html mode by going to
|
||||
\http://<server-address>/report/html/sale.report_saleorder/38
|
||||
|
||||
Or you can access the pdf version at
|
||||
\http://<server-address>/report/pdf/sale.report_saleorder/38
|
||||
|
||||
.. _wkhtmltopdf: http://wkhtmltopdf.org
|
||||
|
||||
@@ -316,7 +316,7 @@ Source installation requires manually installing dependencies:
|
||||
they require compilation.
|
||||
- add ``pypiwin32`` because it's needed under windows.
|
||||
|
||||
Then use pip to install install the dependencies using the following
|
||||
Then use pip to install the dependencies using the following
|
||||
command from a cmd.exe prompt (replace ``\YourOdooPath`` by the actual
|
||||
path where you downloaded Odoo):
|
||||
|
||||
|
||||
@@ -591,17 +591,15 @@ class ir_actions_server(osv.osv):
|
||||
'condition': 'True',
|
||||
'type': 'ir.actions.server',
|
||||
'sequence': 5,
|
||||
'code': """# You can use the following variables:
|
||||
# - self: ORM model of the record on which the action is triggered
|
||||
'code': """# Available locals:
|
||||
# - time, datetime, dateutil: Python libraries
|
||||
# - env: Odoo Environement
|
||||
# - model: Model of the record on which the action is triggered
|
||||
# - object: Record on which the action is triggered if there is one, otherwise None
|
||||
# - pool: ORM model pool (i.e. self.pool)
|
||||
# - cr: database cursor
|
||||
# - uid: current user id
|
||||
# - context: current context
|
||||
# - time: Python time module
|
||||
# - workflow: Workflow engine
|
||||
# - log : log(message), function to log debug information in logging table
|
||||
# If you plan to return an action, assign: action = {...}""",
|
||||
# - Warning: Warning Exception to use with raise
|
||||
# To return an action, assign: action = {...}""",
|
||||
'use_relational_model': 'base',
|
||||
'use_create': 'new',
|
||||
'use_write': 'current',
|
||||
@@ -971,18 +969,30 @@ class ir_actions_server(osv.osv):
|
||||
|
||||
eval_context = super(ir_actions_server, self)._get_eval_context(cr, uid, action=action, context=context)
|
||||
obj_pool = self.pool[action.model_id.model]
|
||||
env = openerp.api.Environment(cr, uid, context)
|
||||
model = env[action.model_id.model]
|
||||
obj = None
|
||||
if context.get('active_model') == action.model_id.model and context.get('active_id'):
|
||||
obj = obj_pool.browse(cr, uid, context['active_id'], context=context)
|
||||
obj = model.browse(context['active_id'])
|
||||
eval_context.update({
|
||||
'self': obj_pool,
|
||||
# orm
|
||||
'env': env,
|
||||
'model': model,
|
||||
'workflow': workflow,
|
||||
# Exceptions
|
||||
'Warning': openerp.exceptions.Warning,
|
||||
# record
|
||||
# TODO: When porting to master move badly named obj and object to
|
||||
# deprecated and define record (active_id) and records (active_ids)
|
||||
'object': obj,
|
||||
'obj': obj,
|
||||
# Deprecated use env or model instead
|
||||
'self': obj_pool,
|
||||
'pool': self.pool,
|
||||
'cr': cr,
|
||||
'context': context,
|
||||
'workflow': workflow,
|
||||
'Warning': openerp.exceptions.Warning,
|
||||
'user': env.user,
|
||||
# helpers
|
||||
'log': log,
|
||||
})
|
||||
return eval_context
|
||||
|
||||
@@ -362,7 +362,7 @@ class ir_attachment(osv.osv):
|
||||
ima.check(cr, uid, model, mode)
|
||||
self.pool[model].check_access_rule(cr, uid, existing_ids, mode, context=context)
|
||||
if require_employee:
|
||||
if not self.pool['res.users'].has_group(cr, uid, 'base.group_user'):
|
||||
if not uid == SUPERUSER_ID and not self.pool['res.users'].has_group(cr, uid, 'base.group_user'):
|
||||
raise AccessError(_("Sorry, you are not allowed to access this document."))
|
||||
|
||||
def _search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
|
||||
|
||||
@@ -363,6 +363,27 @@ class ir_mail_server(osv.osv):
|
||||
msg.attach(part)
|
||||
return msg
|
||||
|
||||
def _get_default_bounce_address(self, cr, uid, context=None):
|
||||
'''Compute the default bounce address.
|
||||
|
||||
The default bounce address is used to set the envelop address if no
|
||||
envelop address is provided in the message. It is formed by properly
|
||||
joining the parameters "mail.catchall.alias" and
|
||||
"mail.catchall.domain".
|
||||
|
||||
If "mail.catchall.alias" is not set it defaults to "postmaster-odoo".
|
||||
|
||||
If "mail.catchall.domain" is not set, return None.
|
||||
|
||||
'''
|
||||
get_param = self.pool['ir.config_parameter'].get_param
|
||||
postmaster = get_param(cr, uid, 'mail.bounce.alias',
|
||||
default='postmaster-odoo',
|
||||
context=context,)
|
||||
domain = get_param(cr, uid, 'mail.catchall.domain', context=context)
|
||||
if postmaster and domain:
|
||||
return '%s@%s' % (postmaster, domain)
|
||||
|
||||
def send_email(self, cr, uid, message, mail_server_id=None, smtp_server=None, smtp_port=None,
|
||||
smtp_user=None, smtp_password=None, smtp_encryption=None, smtp_debug=False,
|
||||
context=None):
|
||||
@@ -378,8 +399,9 @@ class ir_mail_server(osv.osv):
|
||||
and fails if not found.
|
||||
|
||||
:param message: the email.message.Message to send. The envelope sender will be extracted from the
|
||||
``Return-Path`` or ``From`` headers. The envelope recipients will be
|
||||
extracted from the combined list of ``To``, ``CC`` and ``BCC`` headers.
|
||||
``Return-Path`` (if present), or will be set to the default bounce address.
|
||||
The envelope recipients will be extracted from the combined list of ``To``,
|
||||
``CC`` and ``BCC`` headers.
|
||||
:param mail_server_id: optional id of ir.mail_server to use for sending. overrides other smtp_* arguments.
|
||||
:param smtp_server: optional hostname of SMTP server to use
|
||||
:param smtp_encryption: optional TLS mode, one of 'none', 'starttls' or 'ssl' (see ir.mail_server fields for explanation)
|
||||
@@ -390,7 +412,14 @@ class ir_mail_server(osv.osv):
|
||||
:return: the Message-ID of the message that was just sent, if successfully sent, otherwise raises
|
||||
MailDeliveryException and logs root cause.
|
||||
"""
|
||||
smtp_from = message['Return-Path'] or message['From']
|
||||
# Use the default bounce address **only if** no Return-Path was
|
||||
# provided by caller. Caller may be using Variable Envelope Return
|
||||
# Path (VERP) to detect no-longer valid email addresses.
|
||||
smtp_from = message['Return-Path']
|
||||
if not smtp_from:
|
||||
smtp_from = self._get_default_bounce_address(cr, uid, context=context)
|
||||
if not smtp_from:
|
||||
smtp_from = message['From']
|
||||
assert smtp_from, "The Return-Path or From header is required for any outbound email"
|
||||
|
||||
# The email's "Envelope From" (Return-Path), and all recipient addresses must only contain ASCII characters.
|
||||
|
||||
@@ -1068,6 +1068,7 @@ class view(osv.osv):
|
||||
if values is None:
|
||||
values = dict()
|
||||
qcontext = dict(
|
||||
env=api.Environment(cr, uid, context),
|
||||
keep_query=keep_query,
|
||||
request=request, # might be unbound if we're not in an httprequest context
|
||||
debug=request.debug if request else False,
|
||||
|
||||
@@ -301,3 +301,22 @@ class res_currency_rate(osv.osv):
|
||||
'name': lambda *a: time.strftime('%Y-%m-%d 00:00:00'),
|
||||
}
|
||||
_order = "name desc"
|
||||
|
||||
def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
|
||||
if operator in ['=', '!=']:
|
||||
try:
|
||||
date_format = '%Y-%m-%d'
|
||||
if context.get('lang'):
|
||||
lang_obj = self.pool['res.lang']
|
||||
lang_ids = lang_obj.search(cr, user, [('code', '=', context['lang'])], context=context)
|
||||
if lang_ids:
|
||||
date_format = lang_obj.browse(cr, user, lang_ids[0], context=context).date_format
|
||||
name = time.strftime('%Y-%m-%d', time.strptime(name, date_format))
|
||||
except ValueError:
|
||||
try:
|
||||
args.append(('rate', operator, float(name)))
|
||||
except ValueError:
|
||||
return []
|
||||
name = ''
|
||||
operator = 'ilike'
|
||||
return super(res_currency_rate, self).name_search(cr, user, name, args=args, operator=operator, context=context, limit=limit)
|
||||
|
||||
+2
-1
@@ -317,8 +317,9 @@ class Field(object):
|
||||
attrs['copy'] = attrs.get('copy', False)
|
||||
attrs['readonly'] = attrs.get('readonly', not attrs.get('inverse'))
|
||||
if attrs.get('related'):
|
||||
# by default, related fields are not stored
|
||||
# by default, related fields are not stored and not copied
|
||||
attrs['store'] = attrs.get('store', False)
|
||||
attrs['copy'] = attrs.get('copy', False)
|
||||
|
||||
# fix for function fields overridden by regular columns
|
||||
if not isinstance(attrs.get('column'), (NoneType, fields.function)):
|
||||
|
||||
+9
-5
@@ -398,14 +398,18 @@ def route(route=None, **kw):
|
||||
response = f(*args, **kw)
|
||||
if isinstance(response, Response) or f.routing_type == 'json':
|
||||
return response
|
||||
elif isinstance(response, werkzeug.wrappers.BaseResponse):
|
||||
|
||||
if isinstance(response, basestring):
|
||||
return Response(response)
|
||||
|
||||
if isinstance(response, werkzeug.exceptions.HTTPException):
|
||||
response = response.get_response()
|
||||
if isinstance(response, werkzeug.wrappers.BaseResponse):
|
||||
response = Response.force_type(response)
|
||||
response.set_default()
|
||||
return response
|
||||
elif isinstance(response, basestring):
|
||||
return Response(response)
|
||||
else:
|
||||
_logger.warn("<function %s.%s> returns an invalid response type for an http request" % (f.__module__, f.__name__))
|
||||
|
||||
_logger.warn("<function %s.%s> returns an invalid response type for an http request" % (f.__module__, f.__name__))
|
||||
return response
|
||||
response_wrap.routing = routing
|
||||
response_wrap.original_func = f
|
||||
|
||||
+2
-2
@@ -188,7 +188,7 @@ def get_pg_type(f, type_override=None):
|
||||
elif issubclass(field_type, (fields.char, fields.reference)):
|
||||
pg_type = ('varchar', pg_varchar(f.size))
|
||||
elif issubclass(field_type, fields.selection):
|
||||
if (isinstance(f.selection, list) and isinstance(f.selection[0][0], int))\
|
||||
if (f.selection and isinstance(f.selection, list) and isinstance(f.selection[0][0], int))\
|
||||
or getattr(f, 'size', None) == -1:
|
||||
pg_type = ('int4', 'INTEGER')
|
||||
else:
|
||||
@@ -2504,7 +2504,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_pg_size and (f.size is None or f_pg_size < f.size):
|
||||
if f_pg_type == 'varchar' and f._type in ('char', 'selection') 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)))
|
||||
|
||||
@@ -31,6 +31,7 @@ import re
|
||||
import sys
|
||||
import time
|
||||
import unittest
|
||||
import threading
|
||||
from os.path import join as opj
|
||||
|
||||
import unittest2
|
||||
@@ -461,6 +462,7 @@ def run_unit_tests(module_name, dbname, position=runs_at_install):
|
||||
global current_test
|
||||
current_test = module_name
|
||||
mods = get_test_modules(module_name)
|
||||
threading.currentThread().testing = True
|
||||
r = True
|
||||
for m in mods:
|
||||
tests = unwrap_suite(unittest2.TestLoader().loadTestsFromModule(m))
|
||||
@@ -478,6 +480,7 @@ def run_unit_tests(module_name, dbname, position=runs_at_install):
|
||||
_logger.error("Module %s: %d failures, %d errors", module_name, len(result.failures), len(result.errors))
|
||||
|
||||
current_test = None
|
||||
threading.currentThread().testing = False
|
||||
return r
|
||||
|
||||
def unwrap_suite(test):
|
||||
|
||||
+21
-5
@@ -48,8 +48,7 @@ class MyOption (optparse.Option, object):
|
||||
self.my_default = attrs.pop('my_default', None)
|
||||
super(MyOption, self).__init__(*opts, **attrs)
|
||||
|
||||
DEFAULT_LOG_HANDLER = [':INFO']
|
||||
|
||||
DEFAULT_LOG_HANDLER = ':INFO'
|
||||
def _get_default_datadir():
|
||||
home = os.path.expanduser('~')
|
||||
if os.path.isdir(home):
|
||||
@@ -62,6 +61,20 @@ def _get_default_datadir():
|
||||
# No "version" kwarg as session and filestore paths are shared against series
|
||||
return func(appname=release.product_name, appauthor=release.author)
|
||||
|
||||
def _deduplicate_loggers(loggers):
|
||||
""" Avoid saving multiple logging levels for the same loggers to a save
|
||||
file, that just takes space and the list can potentially grow unbounded
|
||||
if for some odd reason people use :option`odoo.py --save`` all the time.
|
||||
"""
|
||||
# dict(iterable) -> the last item of iterable for any given key wins,
|
||||
# which is what we want and expect. Output order should not matter as
|
||||
# there are no duplicates within the output sequence
|
||||
return (
|
||||
'{}:{}'.format(logger, level)
|
||||
for logger, level in dict(it.split(':') for it in loggers).iteritems()
|
||||
)
|
||||
|
||||
|
||||
class configmanager(object):
|
||||
def __init__(self, fname=None):
|
||||
"""Constructor.
|
||||
@@ -160,7 +173,7 @@ class configmanager(object):
|
||||
group.add_option("--logfile", dest="logfile", help="file where the server log will be stored")
|
||||
group.add_option("--logrotate", dest="logrotate", action="store_true", my_default=False, help="enable logfile rotation")
|
||||
group.add_option("--syslog", action="store_true", dest="syslog", my_default=False, help="Send the log to the syslog server")
|
||||
group.add_option('--log-handler', action="append", default=DEFAULT_LOG_HANDLER, my_default=DEFAULT_LOG_HANDLER, metavar="PREFIX:LEVEL", help='setup a handler at LEVEL for a given PREFIX. An empty PREFIX indicates the root logger. This option can be repeated. Example: "openerp.orm:DEBUG" or "werkzeug:CRITICAL" (default: ":INFO")')
|
||||
group.add_option('--log-handler', action="append", default=[], my_default=DEFAULT_LOG_HANDLER, metavar="PREFIX:LEVEL", help='setup a handler at LEVEL for a given PREFIX. An empty PREFIX indicates the root logger. This option can be repeated. Example: "openerp.orm:DEBUG" or "werkzeug:CRITICAL" (default: ":INFO")')
|
||||
group.add_option('--log-request', action="append_const", dest="log_handler", const="openerp.http.rpc.request:DEBUG", help='shortcut for --log-handler=openerp.http.rpc.request:DEBUG')
|
||||
group.add_option('--log-response', action="append_const", dest="log_handler", const="openerp.http.rpc.response:DEBUG", help='shortcut for --log-handler=openerp.http.rpc.response:DEBUG')
|
||||
group.add_option('--log-web', action="append_const", dest="log_handler", const="openerp.http:DEBUG", help='shortcut for --log-handler=openerp.http:DEBUG')
|
||||
@@ -369,14 +382,14 @@ class configmanager(object):
|
||||
'email_from', 'smtp_server', 'smtp_user', 'smtp_password',
|
||||
'db_maxconn', 'import_partial', 'addons_path',
|
||||
'xmlrpc', 'syslog', 'without_demo',
|
||||
'dbfilter', 'log_handler', 'log_level', 'log_db',
|
||||
'dbfilter', 'log_level', 'log_db',
|
||||
'geoip_database',
|
||||
]
|
||||
|
||||
for arg in keys:
|
||||
# Copy the command-line argument (except the special case for log_handler, due to
|
||||
# action=append requiring a real default, so we cannot use the my_default workaround)
|
||||
if getattr(opt, arg) and getattr(opt, arg) != DEFAULT_LOG_HANDLER:
|
||||
if getattr(opt, arg):
|
||||
self.options[arg] = getattr(opt, arg)
|
||||
# ... or keep, but cast, the config file value.
|
||||
elif isinstance(self.options[arg], basestring) and self.casts[arg].type in optparse.Option.TYPE_CHECKER:
|
||||
@@ -384,6 +397,7 @@ class configmanager(object):
|
||||
|
||||
if isinstance(self.options['log_handler'], basestring):
|
||||
self.options['log_handler'] = self.options['log_handler'].split(',')
|
||||
self.options['log_handler'].extend(opt.log_handler)
|
||||
|
||||
# if defined but None take the configfile value
|
||||
keys = [
|
||||
@@ -566,6 +580,8 @@ class configmanager(object):
|
||||
continue
|
||||
if opt in ('log_level',):
|
||||
p.set('options', opt, loglevelnames.get(self.options[opt], self.options[opt]))
|
||||
elif opt == 'log_handler':
|
||||
p.set('options', opt, ','.join(_deduplicate_loggers(self.options[opt])))
|
||||
else:
|
||||
p.set('options', opt, self.options[opt])
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ condition/math builtins.
|
||||
# - safe_eval in tryton http://hg.tryton.org/hgwebdir.cgi/trytond/rev/bbb5f73319ad
|
||||
|
||||
from opcode import HAVE_ARGUMENT, opmap, opname
|
||||
from psycopg2 import OperationalError
|
||||
from types import CodeType
|
||||
import logging
|
||||
|
||||
@@ -321,6 +322,10 @@ def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=Fal
|
||||
raise
|
||||
except openerp.exceptions.AccessError:
|
||||
raise
|
||||
except OperationalError:
|
||||
# Do not hide PostgreSQL low-level exceptions, to let the auto-replay
|
||||
# of serialized transactions work its magic
|
||||
raise
|
||||
except openerp.exceptions.MissingError:
|
||||
raise
|
||||
except Exception, e:
|
||||
|
||||
Reference in New Issue
Block a user