[IMP] base ir.qweb: compile template (and use ast) to improve rendering speed
* The compiled templates are cached per user, lang, inherit context values * ir.ui.fields: attributes method return an dict, and record_to_html return only the content value of the field * all rendered text use build_text and all attributes use build_attribute * t-esc-options is removed and replace by format_value method * AssetsBundle receive the list files and remains
This commit is contained in:
@@ -50,13 +50,13 @@
|
||||
</tr>
|
||||
<tr t-if="get_partner_lines">
|
||||
<th>Account Total</th>
|
||||
<th class="text-right"><span t-esc="get_direction[6]" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></th>
|
||||
<th class="text-right"><span t-esc="get_direction[4]" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></th>
|
||||
<th class="text-right"><span t-esc="get_direction[3]" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></th>
|
||||
<th class="text-right"><span t-esc="get_direction[2]" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></th>
|
||||
<th class="text-right"><span t-esc="get_direction[1]" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></th>
|
||||
<th class="text-right"><span t-esc="get_direction[0]" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></th>
|
||||
<th class="text-right"><span t-esc="get_direction[5]" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></th>
|
||||
<th class="text-right"><span t-esc="format(get_direction[6], 'monetary', res_company.currency_id)"/></th>
|
||||
<th class="text-right"><span t-esc="format(get_direction[4], 'monetary', res_company.currency_id)"/></th>
|
||||
<th class="text-right"><span t-esc="format(get_direction[3], 'monetary', res_company.currency_id)"/></th>
|
||||
<th class="text-right"><span t-esc="format(get_direction[2], 'monetary', res_company.currency_id)"/></th>
|
||||
<th class="text-right"><span t-esc="format(get_direction[1], 'monetary', res_company.currency_id)"/></th>
|
||||
<th class="text-right"><span t-esc="format(get_direction[0], 'monetary', res_company.currency_id)"/></th>
|
||||
<th class="text-right"><span t-esc="format(get_direction[5], 'monetary', res_company.currency_id)"/></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -65,25 +65,25 @@
|
||||
<span t-esc="partner['name']"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="partner['direction']" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(partner['direction'], 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="partner['4']" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(partner['4'], 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="partner['3']" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(partner['3'], 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="partner['2']" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(partner['2'], 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="partner['1']" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(partner['1'], 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="partner['0']" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(partner['0'], 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="partner['total']" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(partner['total'], 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -43,13 +43,13 @@
|
||||
<span t-att-style="style" t-esc="a.get('name')"/>
|
||||
</td>
|
||||
<td class="text-right" style="white-space: text-nowrap;">
|
||||
<span t-att-style="style" t-esc="a.get('debit')" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-att-style="style" t-esc="format(a.get('debit'), 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right" style="white-space: text-nowrap;">
|
||||
<span t-att-style="style" t-esc="a.get('credit')" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-att-style="style" t-esc="format(a.get('credit'), 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right" style="white-space: text-nowrap;">
|
||||
<span t-att-style="style" t-esc="a.get('balance')" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-att-style="style" t-esc="format(a.get('balance'), 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</t>
|
||||
</tr>
|
||||
@@ -73,7 +73,7 @@
|
||||
<span style="color: white;" t-esc="'..' * a.get('level', 0)"/>
|
||||
<span t-att-style="style" t-esc="a.get('name')"/>
|
||||
</td>
|
||||
<td class="text-right"><span t-att-style="style" t-esc="a.get('balance')" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td class="text-right"><span t-att-style="style" t-esc="format(a.get('balance'), 'monetary', res_company.currency_id)"/></td>
|
||||
</t>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -96,7 +96,7 @@
|
||||
<span style="color: white;" t-esc="'..'"/>
|
||||
<span t-att-style="style" t-esc="a.get('name')"/>
|
||||
</td>
|
||||
<td class="text-right"><span t-att-style="style" t-esc="a.get('balance')" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td class="text-right"><span t-att-style="style" t-esc="format(a.get('balance'), 'monetary', res_company.currency_id)"/></td>
|
||||
<td class="text-right"><span t-att-style="style" t-esc="a.get('balance_cmp')"/></td>
|
||||
</t>
|
||||
</tr>
|
||||
|
||||
@@ -66,13 +66,13 @@
|
||||
<span t-esc="account['name']"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="account['debit']" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(account['debit'], 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="account['credit']" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(account['credit'], 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="account['balance']" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(account['balance'], 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td groups="base.group_multi_currency"/>
|
||||
</tr>
|
||||
@@ -84,13 +84,13 @@
|
||||
<td><span t-esc="line['move_name']"/></td>
|
||||
<td><span t-esc="line['lname']"/></td>
|
||||
<td class="text-right">
|
||||
<span t-esc="line['debit']" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(line['debit'], 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="line['credit']" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(line['credit'], 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="line['balance']" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(line['balance'], 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right" groups="base.group_multi_currency">
|
||||
<span t-esc="line['amount_currency'] if line['amount_currency'] > 0.00 else ''"/>
|
||||
|
||||
@@ -51,10 +51,10 @@
|
||||
<td><span t-field="aml.account_id.code"/></td>
|
||||
<td><span t-esc="aml.partner_id and aml.partner_id.name[:23] or ''"/></td>
|
||||
<td><span t-esc="aml.name[:35]"/></td>
|
||||
<td><span t-esc="aml.debit" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="aml.credit" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(aml.debit, 'monetary', res_company.currency_id)"/></td>
|
||||
<td><span t-esc="format(aml.credit, 'monetary', res_company.currency_id)"/></td>
|
||||
<td t-if="data['form']['amount_currency'] and aml.amount_currency">
|
||||
<span t-esc="aml.amount_currency" t-esc-options='{"widget": "monetary", "display_currency": "aml.currency_id"}'/>
|
||||
<span t-esc="format(aml.amount_currency, 'monetary', aml.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -65,8 +65,8 @@
|
||||
<table class="table table-condensed">
|
||||
<tr>
|
||||
<td><strong>Total</strong></td>
|
||||
<td><span t-esc="sum_debit(data, o)" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="sum_credit(data, o)" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(sum_debit(data, o), 'monetary', res_company.currency_id)"/></td>
|
||||
<td><span t-esc="format(sum_credit(data, o), 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
@@ -87,8 +87,8 @@
|
||||
<t t-set="taxes" t-value="get_taxes(data, o)"/>
|
||||
<tr t-foreach="taxes" t-as="tax">
|
||||
<td><span t-esc="tax.name"/></td>
|
||||
<td><span t-esc="taxes[tax]['base_amount']" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="taxes[tax]['tax_amount']" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(taxes[tax]['base_amount'], 'monetary', res_company.currency_id)"/></td>
|
||||
<td><span t-esc="format(taxes[tax]['tax_amount'], 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -44,10 +44,10 @@
|
||||
<span t-esc="line['ref']"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="line['debit']" t-esc-options='{"widget": "monetary", "display_currency": "currency"}'/>
|
||||
<span t-esc="format(line['debit'], 'monetary', currency)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="line['credit']" t-esc-options='{"widget": "monetary", "display_currency": "currency"}'/>
|
||||
<span t-esc="format(line['credit'], 'monetary', currency)"/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span t-if="line['blocked']">x</span>
|
||||
@@ -56,16 +56,16 @@
|
||||
<tr>
|
||||
<td colspan="3"></td>
|
||||
<td><strong>Sub-Total :</strong></td>
|
||||
<td class="text-right"><span t-esc="Totals[o.id][currency]['due']" t-esc-options='{"widget": "monetary", "display_currency": "currency"}'/></td>
|
||||
<td class="text-right"><span t-esc="Totals[o.id][currency]['paid']" t-esc-options='{"widget": "monetary", "display_currency": "currency"}'/></td>
|
||||
<td class="text-right"><span t-esc="Totals[o.id][currency]['mat']" t-esc-options='{"widget": "monetary", "display_currency": "currency"}'/></td>
|
||||
<td class="text-right"><span t-esc="format(Totals[o.id][currency]['due'], 'monetary', currency)"/></td>
|
||||
<td class="text-right"><span t-esc="format(Totals[o.id][currency]['paid'], 'monetary', currency)"/></td>
|
||||
<td class="text-right"><span t-esc="format(Totals[o.id][currency]['mat'], 'monetary', currency)"/></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3"></td>
|
||||
<td><strong>Balance :</strong></td>
|
||||
<td class="text-right">
|
||||
<span t-esc="Totals[o.id][currency]['due'] - Totals[o.id][currency]['paid']" t-esc-options='{"widget": "monetary", "display_currency": "currency"}'/>
|
||||
<span t-esc="format(Totals[o.id][currency]['due'] - Totals[o.id][currency]['paid'], 'monetary', currency)"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -46,13 +46,13 @@
|
||||
- <strong t-esc="o.name"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<strong t-esc="sum_partner(data, o, 'debit')" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<strong t-esc="format(sum_partner(data, o, 'debit'), 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<strong t-esc="sum_partner(data, o, 'credit')" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<strong t-esc="format(sum_partner(data, o, 'credit'), 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<strong t-esc="sum_partner(data, o, 'debit - credit')" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<strong t-esc="format(sum_partner(data, o, 'debit - credit'), 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr t-foreach="lines(data, o)" t-as="line">
|
||||
@@ -69,16 +69,16 @@
|
||||
<span t-esc="line['displayed_name']"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="line['debit']" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(line['debit'], 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="line['credit']" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(line['credit'], 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="line['progress']" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(line['progress'], 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td t-if="amount_currency">
|
||||
<span t-esc="line['amount_currency']" t-esc-options='{"widget": "monetary", "display_currency": "line[\"currency_id\"]"}'/>
|
||||
<span t-esc="format(line['amount_currency'], 'monetary', line['currency_id'])"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -50,13 +50,13 @@
|
||||
<span t-att-style="style" t-esc="account['name']"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-att-style="style" t-esc="account['debit']" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-att-style="style" t-esc="format(account['debit'], 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-att-style="style" t-esc="account['credit']" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-att-style="style" t-esc="format(account['credit'], 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-att-style="style" t-esc="account['balance']" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-att-style="style" t-esc="format(account['balance'], 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -33,11 +33,11 @@
|
||||
<tr>
|
||||
<td>Capital souscrit - non appelé</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar1" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar1, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td></td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar1" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar1, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -59,83 +59,83 @@
|
||||
<tr>
|
||||
<td>Frais d'établissement</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar2" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar2, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-bavar2b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-bavar2b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar2+bavar2b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar2+bavar2b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Frais de recherche et de développement</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar3" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar3, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-bavar3b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-bavar3b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar3+bavar3b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar3+bavar3b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Concessions, brevets, licences,..., droits et valeurs similaires</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar4" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar4, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-bavar4b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-bavar4b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar4+bavar4b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar4+bavar4b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Fonds commercial</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar5" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar5, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-bavar5b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-bavar5b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar5+bavar5b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar5+bavar5b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Autres</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar6" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar6, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-bavar6b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-bavar6b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar6+bavar6b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar6+bavar6b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Immobilisations incorporelles en cours</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar7" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar7, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-bavar7b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-bavar7b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar7+bavar7b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar7+bavar7b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Avances et acomptes</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar8" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar8, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td></td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar8" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar8, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -149,71 +149,71 @@
|
||||
<tr>
|
||||
<td>Terrains</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar9" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar9, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-bavar9b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-bavar9b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar9+bavar9b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar9+bavar9b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Constructions</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar10" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar10, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-bavar10b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-bavar10b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar10+bavar10b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar10+bavar10b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Installations techniques,matériel et outillage</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar11" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar11, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-bavar11b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-bavar11b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar11+bavar11b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar11+bavar11b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Autres</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar12" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar12, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-bavar12b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-bavar12b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar12+bavar12b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar12+bavar12b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Immobilisations corporelles en cours</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar13" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar13, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-bavar13b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-bavar13b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar13+bavar13b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar13+bavar13b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Avances et acomptes</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar14" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar14, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td></td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar14" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar14, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -227,73 +227,73 @@
|
||||
<tr>
|
||||
<td>Participations</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar15" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar15, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-bavar15b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-bavar15b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar15+bavar15b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar15+bavar15b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Créances rattachées à des participations</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar16" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar16, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-bavar16b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-bavar16b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar16+bavar16b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar16+bavar16b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Titres immobilisés de l'activité de portefeuille</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar17" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar17, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-bavar17b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-bavar17b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar17+bavar17b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar17+bavar17b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Autres titres immobilisés</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar18" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar18, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-bavar18b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-bavar18b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar18+bavar18b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar18+bavar18b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Prêts</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar19" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar19, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-bavar19b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-bavar19b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar19+bavar19b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar19+bavar19b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Autres</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar20" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar20, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-bavar20b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-bavar20b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar20+bavar20b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar20+bavar20b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -301,13 +301,13 @@
|
||||
<strong>TOTAL I</strong>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="at1a" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(at1a, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-at1b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-at1b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="at1" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(at1, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -325,59 +325,59 @@
|
||||
<tr>
|
||||
<td>Matières premières et autres approvisionnements</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar21" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar21, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-bavar21b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-bavar21b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar21+bavar21b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar21+bavar21b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>En-cours de production [biens et services]</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar22" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar22, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-bavar22b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-bavar22b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar22+bavar22b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar22+bavar22b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Produits intermédiaires et finis</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar23" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar23, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-bavar23b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-bavar23b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar23+bavar23b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar23+bavar23b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Marchandises</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar24" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar24, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-bavar24b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-bavar24b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar24+bavar24b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar24+bavar24b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Avances et acomptes versés sur commandes</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar25" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar25, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td></td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar25" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar25, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -389,35 +389,35 @@
|
||||
<tr>
|
||||
<td>Créances clients et comptes rattachés</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar26" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar26, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-bavar26b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-bavar26b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar26+bavar26b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar26+bavar26b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Autres</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar27" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar27, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-bavar27b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-bavar27b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar27+bavar27b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar27+bavar27b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Capital souscrit - appelé , non versé</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar28" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar28, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td></td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar28" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar28, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -429,55 +429,55 @@
|
||||
<tr>
|
||||
<td>Actions propres</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar29" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar29, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-bavar29b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-bavar29b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar29+bavar29b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar29+bavar29b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Autres titres</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar30" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar30, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-bavar30b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-bavar30b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar30+bavar30b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar30+bavar30b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Instruments de trésorerie</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar31" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar31, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td></td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar31" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar31, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Disponibilités</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar32" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar32, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td></td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar32" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar32, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Charges constatés d'avance</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar33" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar33, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td></td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar33" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar33, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -485,43 +485,43 @@
|
||||
<strong>TOTAL II</strong>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="at2a" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(at2a, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-at2b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-at2b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="at2" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(at2, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Charges à répartir sur plusieurs exercices ( III )</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar34" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar34, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td></td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar34" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar34, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Primes de remboursement des emprunts ( IV )</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar35" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar35, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td></td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar35" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar35, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Écarts de conversion actif ( V )</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar36" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar36, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td></td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bavar36" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(bavar36, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -529,13 +529,13 @@
|
||||
<strong>TOTAL ACTIF ( I + II + III + IV + V )</strong>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="at1a+at2a" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(at1a+at2a, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="-at1b-at2b" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(-at1b-at2b, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="actif" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span t-esc="format(actif, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -549,19 +549,19 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Capital [dont versé...]</td>
|
||||
<td><span t-esc="bpvar1" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(bpvar1, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Primes d'émission, de fusion, d'apport</td>
|
||||
<td><span t-esc="bpvar2" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(bpvar2, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Écarts de réévaluation</td>
|
||||
<td><span t-esc="bpvar3" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(bpvar3, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Écart d'équivalence</td>
|
||||
<td><span t-esc="bpvar4" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(bpvar4, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>RÉSERVES</strong></td>
|
||||
@@ -569,37 +569,37 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Réserve légale</td>
|
||||
<td><span t-esc="bpvar5" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(bpvar5, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Réserves statutaires ou contractuelles</td>
|
||||
<td><span t-esc="bpvar6" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(bpvar6, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Réserves réglementées</td><td><span t-esc="bpvar7" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td></tr>
|
||||
<td>Réserves réglementées</td><td><span t-esc="format(bpvar7, 'monetary', res_company.currency_id)"/></td></tr>
|
||||
<tr>
|
||||
<td>Autres réserves</td>
|
||||
<td><span t-esc="bpvar8" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(bpvar8, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Report à nouveau</td>
|
||||
<td><span t-esc="bpvar9" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(bpvar9, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>RÉSULTAT DE L'EXERCICE [bénéfice ou perte]</strong></td>
|
||||
<td><span t-esc="bpvar10" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(bpvar10, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Subventions d'investissement</td>
|
||||
<td><span t-esc="bpvar11" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(bpvar11, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Provisions réglementées</td>
|
||||
<td><span t-esc="bpvar12" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(bpvar12, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-right"><strong>TOTAL I</strong></td>
|
||||
<td><span t-esc="pt1" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(pt1, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>PROVISIONS</strong></td>
|
||||
@@ -607,15 +607,15 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Provisions pour risques</td>
|
||||
<td><span t-esc="bpvar13" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(bpvar13, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Provisions pour charges</td>
|
||||
<td><span t-esc="bpvar14" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(bpvar14, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-right"><strong>TOTAL II</strong></td>
|
||||
<td><span t-esc="pt2" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(pt2, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>DETTES</strong></td>
|
||||
@@ -623,59 +623,59 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Emprunts obligataires convertibles</td>
|
||||
<td><span t-esc="bpvar15" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(bpvar15, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Autres emprunts obligataires</td>
|
||||
<td><span t-esc="bpvar16" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(bpvar16, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Emprunts et dettes auprès des établissements de crédit</td>
|
||||
<td><span t-esc="bpvar17" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(bpvar17, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Emprunts et dettes financières diverses</td>
|
||||
<td><span t-esc="bpvar18" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(bpvar18, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Avances et acomptes reçus sur commandes en cours</td>
|
||||
<td><span t-esc="bpvar19" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(bpvar19, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dettes fournisseurs et comptes rattachés </td>
|
||||
<td><span t-esc="bpvar20" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(bpvar20, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dettes fiscales et sociales</td>
|
||||
<td><span t-esc="bpvar21" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(bpvar21, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dettes sur immobilisations et comptes rattachés</td>
|
||||
<td><span t-esc="bpvar22" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(bpvar22, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Autres dettes</td>
|
||||
<td><span t-esc="bpvar23" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(bpvar23, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Instruments de trésorerie</td>
|
||||
<td><span t-esc="bpvar24" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(bpvar24, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Produits constatés d'avance</td>
|
||||
<td><span t-esc="bpvar25" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(bpvar25, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-right"><strong>TOTAL III</strong></td>
|
||||
<td><span t-esc="pt3" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(pt3, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Écarts de conversion passif <font face="Times-Roman">( IV )</font></td>
|
||||
<td><span t-esc="bpvar26" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(bpvar26, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>TOTAL GÉNÉRAL (I + II + III + IV)</strong></td>
|
||||
<td><span t-esc="passif" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(passif, 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>&nbsp;</td>
|
||||
@@ -683,7 +683,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>ACTIF - PASSIF</strong></td>
|
||||
<td><span t-esc="round(actif-passif,2)" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
<td><span t-esc="format(round(actif-passif,2), 'monetary', res_company.currency_id)"/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -35,61 +35,61 @@
|
||||
<tr>
|
||||
<td>Achat de marchandises</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrc1" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrc1, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Variation des stocks</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrc2" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrc2, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Achats de matières premières et autres approvisionnements</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrc3" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrc3, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Variation des stocks</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrc4" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrc4, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Autres achats et charges externes</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrc5" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrc5, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Redevances de crédit-bail mobilier</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrc6" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrc6, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Redevances de crédit-bail immobilier</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrc7" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrc7, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Impôts, taxes et versements assimilés</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrc8" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrc8, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Salaires et traitements</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrc9" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrc9, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Charges sociales</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrc10" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrc10, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -99,43 +99,43 @@
|
||||
<tr>
|
||||
<td>Sur immobilisations : dotations aux amortissements</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrc11" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrc11, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sur immobilisations : dotations aux dépréciations</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrc12" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrc12, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sur actif circulant : dotations aux dépréciations</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrc13" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrc13, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dotations aux provisions</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrc14" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrc14, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Autres charges</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrc15" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrc15, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-right"><strong>TOTAL I</strong></td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="ct1" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(ct1, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Quotes-parts de résultat sur opérations faites en commun ( II )</strong></td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrc16" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrc16, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -145,31 +145,31 @@
|
||||
<tr>
|
||||
<td>Dotations aux amortissements, aux dépréciations et aux provisions</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrc17" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrc17, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Intérêts et charges assimilées</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrc18" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrc18, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Différences négatives de change</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrc19" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrc19, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Charges nettes sur cessions de valeurs mobilières de placement</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrc20" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrc20, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-right"><strong>TOTAL III</strong></td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="ct3" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(ct3, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -179,43 +179,43 @@
|
||||
<tr>
|
||||
<td>Sur opérations de gestion</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrc21" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrc21, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sur opérations en capital</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrc22" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrc22, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dotations aux amortissements, aux dépréciations et aux provisions</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrc23" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrc23, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-right"><strong>TOTAL IV</strong></td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="ct4" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(ct4, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Participation des salariés aux résultats ( V )</strong></td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrc24" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrc24, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Impôts sur les bénéfices ( VI )</strong></td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrc25" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrc25, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-right"><strong>TOTAL CHARGES ( I + II + III + IV+ V+ VI )</strong></td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="charges" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(charges, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -236,67 +236,67 @@
|
||||
<tr>
|
||||
<td>Vente de marchandises</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrp1" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrp1, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Production vendue [biens et services]</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrp2" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrp2, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Sous-total A - Montant net du chiffre d'affaires</strong></td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="pta" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(pta, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Production stockée</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrp3" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrp3, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Production immobilisée</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrp4" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrp4, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Subventions d'exploitation</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrp5" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrp5, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Reprises sur provisions, dépréciations (et amortissements) et transferts de charges</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrp6" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrp6, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Autres produits</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrp7" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrp7, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Sous-total B</strong></td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="ptb" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(ptb, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-right"><strong>TOTAL I ( A + B )</strong></td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="pt1" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(pt1, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Quotes-parts de résultat sur opérations faites en commun (II)</strong></td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrp8" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrp8, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -306,43 +306,43 @@
|
||||
<tr>
|
||||
<td>De participation</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrp9" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrp9, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>D'autres valeurs mobilières et créances de l'actif immobilisé</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrp10" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrp10, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Autres intérêts et produits assimilés</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrp11" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrp11, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Reprises sur provisions, dépréciations et transferts de charges</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrp12" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrp12, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Différences positives de change</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrp13" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrp13, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Produits nets sur cessions de valeurs mobilières de placement</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrp14" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrp14, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-right"><strong>TOTAL III</strong></td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="pt3" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(pt3, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -352,37 +352,37 @@
|
||||
<tr>
|
||||
<td>Sur opérations de gestion</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrp15" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrp15, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sur opérations en capital</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrp16" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrp16, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Reprises sur provisions, dépréciations et transferts de charges</td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="cdrp17" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(cdrp17, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-right"><strong>TOTAL IV</strong></td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="pt4" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(pt4, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-right"><strong>TOTAL DES PRODUITS ( I + II + III + IV )</strong></td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="produits" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(produits, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-right"><strong>PRODUITS - CHARGES</strong></td>
|
||||
<td>
|
||||
<span class="text-right" t-esc="produits-charges" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
|
||||
<span class="text-right" t-esc="format(produits-charges, 'monetary', res_company.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from reportlab.graphics.barcode import createBarcodeDrawing
|
||||
|
||||
from openerp.osv import osv
|
||||
from openerp.addons.base.ir.ir_qweb import HTMLSafe
|
||||
from openerp.addons.base.ir.ir_qweb import unicodifier
|
||||
|
||||
|
||||
class BarcodeConverter(osv.AbstractModel):
|
||||
@@ -13,12 +13,13 @@ class BarcodeConverter(osv.AbstractModel):
|
||||
_inherit = 'ir.qweb.field'
|
||||
|
||||
def value_to_html(self, cr, uid, value, field, options=None, context=None):
|
||||
options = options or {}
|
||||
barcode_type = options.get('type', 'Code128')
|
||||
barcode = self.pool['report'].barcode(
|
||||
barcode_type,
|
||||
value,
|
||||
**dict((key, value) for key, value in options.items() if key in ['width', 'height', 'humanreadable']))
|
||||
return HTMLSafe('<img src="data:%s;base64,%s">' % ('png', barcode.encode('base64')))
|
||||
return unicodifier('<img src="data:%s;base64,%s">' % ('png', barcode.encode('base64')))
|
||||
|
||||
def from_html(self, cr, uid, model, field, element, context=None):
|
||||
return None
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
<td colspan="6">
|
||||
<strong>Subtotal: </strong>
|
||||
<t t-set="subtotal" t-value="sum(line.price_subtotal for line in layout_category['lines'])"/>
|
||||
<span t-esc="subtotal" t-esc-options='{"widget": "monetary", "display_currency": "o.currency_id"}'/>
|
||||
<span t-esc="format(subtotal, 'monetary', o.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
<td colspan="6">
|
||||
<strong>Subtotal: </strong>
|
||||
<t t-set="subtotal" t-value="sum(line.price_subtotal for line in layout_category['lines'])"/>
|
||||
<span t-esc="subtotal" t-esc-options='{"widget": "monetary", "display_currency": "doc.pricelist_id.currency_id"}'/>
|
||||
<span t-esc="format(subtotal, 'monetary', doc.pricelist_id.currency_id)"/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
|
||||
@@ -32,7 +32,7 @@ except ImportError:
|
||||
|
||||
import openerp
|
||||
import openerp.modules.registry
|
||||
from openerp.addons.base.ir.ir_qweb import AssetsBundle, QWebTemplateNotFound
|
||||
from openerp.addons.base.ir.ir_qweb import AssetsBundle
|
||||
from openerp.modules import get_resource_path
|
||||
from openerp.tools import topological_sort
|
||||
from openerp.tools.translate import _
|
||||
|
||||
+100
-120
@@ -6,6 +6,7 @@ as well as render a few fields differently.
|
||||
Also, adds methods to convert values back to openerp models.
|
||||
"""
|
||||
|
||||
import ast
|
||||
import cStringIO
|
||||
import datetime
|
||||
import itertools
|
||||
@@ -23,6 +24,8 @@ from lxml import etree, html
|
||||
from PIL import Image as I
|
||||
import openerp.modules
|
||||
|
||||
from odoo import api
|
||||
|
||||
import openerp
|
||||
from openerp.osv import orm, fields
|
||||
from openerp.tools import ustr, DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
|
||||
@@ -43,20 +46,26 @@ class QWeb(orm.AbstractModel):
|
||||
|
||||
re_remove_spaces = re.compile('\s+')
|
||||
|
||||
def render_tag_snippet(self, element, template_attributes, generated_attributes, qwebcontext):
|
||||
cr = qwebcontext['request'].cr
|
||||
uid = qwebcontext['request'].uid
|
||||
view_reg = self.pool['ir.ui.view']
|
||||
page_xmlid = self.eval_format(template_attributes["snippet"], qwebcontext)
|
||||
view = view_reg.browse(cr, uid, view_reg.search(cr, uid, [('key', '=', page_xmlid)]))
|
||||
generated_attributes += " " + "".join([
|
||||
'data-oe-name="%s"' % view.name,
|
||||
'data-oe-type="snippet"',
|
||||
'data-oe-thumbnail="%s"' % template_attributes.get('thumbnail', "oe-thumbnail")])
|
||||
def _compile_directive_snippet(self, el, ast_calls):
|
||||
el.set('t-call', el.attrib.pop('t-snippet'))
|
||||
qwebcontext = ir_qweb.QWebContext(self.env, {})
|
||||
name = self.env['ir.ui.view'].search([('key', '=', el.attrib.get('t-call'))]).display_name
|
||||
thumbnail = el.attrib.pop('t-thumbnail', "oe-thumbnail")
|
||||
div = u'<div name="%s" data-oe-type="snippet" data-oe-thumbnail="%s">' % (escape(ir_qweb.unicodifier(name)), escape(ir_qweb.unicodifier(thumbnail)))
|
||||
return [self._append(ast.Str(div))] + self._compile_node(el, ast_calls) + [self._append(ast.Str(u'</div>'))]
|
||||
|
||||
template_attributes['call'] = template_attributes['snippet']
|
||||
return self.render_tag_call(element, template_attributes, generated_attributes, qwebcontext)
|
||||
def _compile_directive_tag(self, el, ast_calls):
|
||||
if el.get('t-placeholder'):
|
||||
el.set('t-att-placeholder', el.attrib.pop('t-placeholder'))
|
||||
return super(QWeb, self)._compile_directive_tag(el, ast_calls)
|
||||
|
||||
def _directives_eval_order(self):
|
||||
directives = super(QWeb, self)._directives_eval_order()
|
||||
directives.insert(directives.index('call'), 'snippet')
|
||||
return directives
|
||||
|
||||
def _nondirectives_ignore(self):
|
||||
return {'t-thumbnail'} | super(QWeb, self)._nondirectives_ignore()
|
||||
|
||||
#------------------------------------------------------
|
||||
# QWeb fields
|
||||
@@ -67,36 +76,28 @@ class Field(orm.AbstractModel):
|
||||
_name = 'ir.qweb.field'
|
||||
_inherit = 'ir.qweb.field'
|
||||
|
||||
def attributes(self, cr, uid, field_name, record, options,
|
||||
source_element, g_att, t_att, qweb_context, context=None):
|
||||
if options is None:
|
||||
options = {}
|
||||
field = record._model._fields[field_name]
|
||||
attrs = []
|
||||
@api.model
|
||||
def attributes(self, record, field_name, options, qwebcontext):
|
||||
attrs = super(Field, self).attributes(record, field_name, options, qwebcontext)
|
||||
field = record._fields[field_name]
|
||||
|
||||
placeholder = options.get('placeholder') \
|
||||
or source_element.get('placeholder') \
|
||||
or getattr(field, 'placeholder', None)
|
||||
placeholder = options.get('placeholder') or getattr(field, 'placeholder', None)
|
||||
if placeholder:
|
||||
attrs.append(('placeholder', placeholder))
|
||||
attrs['placeholder'] = placeholder
|
||||
|
||||
if context and context.get('edit_translations') and context.get('translatable') and field.type in ('char', 'text') and field.translate:
|
||||
if options['translate'] and field.type in ('char', 'text'):
|
||||
name = "%s,%s" % (record._model._name, field_name)
|
||||
domain = [('name', '=', name), ('res_id', '=', record.id), ('type', '=', 'model'), ('lang', '=', context.get('lang'))]
|
||||
domain = [('name', '=', name), ('res_id', '=', record.id), ('type', '=', 'model'), ('lang', '=', qwebcontext.env.context.get('lang'))]
|
||||
translation = record.env['ir.translation'].search(domain, limit=1)
|
||||
attrs.append(('data-oe-translation-state', translation and translation.state or 'to_translate'))
|
||||
attrs['data-oe-translation-state'] = translation and translation.state or 'to_translate'
|
||||
|
||||
return itertools.chain(
|
||||
super(Field, self).attributes(cr, uid, field_name, record, options,
|
||||
source_element, g_att, t_att,
|
||||
qweb_context, context=context),
|
||||
attrs
|
||||
)
|
||||
return attrs
|
||||
|
||||
def value_from_string(self, value):
|
||||
return value
|
||||
|
||||
def from_html(self, cr, uid, model, field, element, context=None):
|
||||
@api.model
|
||||
def from_html(self, model, field, element):
|
||||
return self.value_from_string(element.text_content().strip())
|
||||
|
||||
|
||||
@@ -111,8 +112,9 @@ class Float(orm.AbstractModel):
|
||||
_name = 'ir.qweb.field.float'
|
||||
_inherit = 'ir.qweb.field.float'
|
||||
|
||||
def from_html(self, cr, uid, model, field, element, context=None):
|
||||
lang = self.user_lang(cr, uid, context=context)
|
||||
@api.model
|
||||
def from_html(self, model, field, element):
|
||||
lang = self.user_lang()
|
||||
|
||||
value = element.text_content().strip()
|
||||
|
||||
@@ -124,32 +126,26 @@ class ManyToOne(orm.AbstractModel):
|
||||
_name = 'ir.qweb.field.many2one'
|
||||
_inherit = 'ir.qweb.field.many2one'
|
||||
|
||||
def attributes(self, cr, uid, field_name, record, options,
|
||||
source_element, g_att, t_att, qweb_context,
|
||||
context=None):
|
||||
attrs = super(ManyToOne, self).attributes(
|
||||
cr, uid, field_name, record, options, source_element, g_att, t_att,
|
||||
qweb_context, context=context)
|
||||
@api.model
|
||||
def attributes(self, record, field_name, options, qwebcontext):
|
||||
attrs = super(ManyToOne, self).attributes(record, field_name, options, qwebcontext)
|
||||
many2one = getattr(record, field_name)
|
||||
if many2one:
|
||||
data = [('data-oe-many2one-id', many2one.id),
|
||||
('data-oe-many2one-model', many2one._name)]
|
||||
return itertools.chain(attrs, data)
|
||||
else:
|
||||
return attrs
|
||||
attrs['data-oe-many2one-id'] = many2one.id
|
||||
attrs['data-oe-many2one-model'] = many2one._name
|
||||
return attrs
|
||||
|
||||
def from_html(self, cr, uid, model, field, element, context=None):
|
||||
Model = self.pool[element.get('data-oe-model')]
|
||||
M2O = self.pool[field.comodel_name]
|
||||
field_name = element.get('data-oe-field')
|
||||
@api.model
|
||||
def from_html(self, model, field, element):
|
||||
Model = self.env[element.get('data-oe-model')]
|
||||
id = int(element.get('data-oe-id'))
|
||||
M2O = self.env[field.comodel_name]
|
||||
field_name = element.get('data-oe-field')
|
||||
many2one_id = int(element.get('data-oe-many2one-id'))
|
||||
|
||||
if many2one_id and M2O.exists(cr, uid, [many2one_id]):
|
||||
record = many2one_id and M2O.browse(many2one_id)
|
||||
if record and record.exists():
|
||||
# save the new id of the many2one
|
||||
Model.write(cr, uid, [id], {
|
||||
field_name: many2one_id
|
||||
}, context=context)
|
||||
Model.browse(id).write({field_name: many2one_id})
|
||||
|
||||
# not necessary, but might as well be explicit about it
|
||||
return None
|
||||
@@ -159,22 +155,18 @@ class Contact(orm.AbstractModel):
|
||||
_name = 'ir.qweb.field.contact'
|
||||
_inherit = 'ir.qweb.field.contact'
|
||||
|
||||
def attributes(self, cr, uid, field_name, record, options,
|
||||
source_element, g_att, t_att, qweb_context,
|
||||
context=None):
|
||||
attrs = super(Contact, self).attributes(
|
||||
cr, uid, field_name, record, options, source_element, g_att, t_att,
|
||||
qweb_context, context=context)
|
||||
if getattr(record, field_name):
|
||||
return itertools.chain(attrs, [('data-oe-contact-options', json.dumps(options))])
|
||||
else:
|
||||
return attrs
|
||||
@api.model
|
||||
def attributes(self, record, field_name, options, qwebcontext):
|
||||
attrs = super(Contact, self).attributes(record, field_name, options, qwebcontext)
|
||||
attrs['data-oe-contact-options'] = json.dumps(options)
|
||||
return attrs
|
||||
|
||||
# helper to call the rendering of contact field
|
||||
def get_record_to_html(self, cr, uid, ids, options=None, context=None):
|
||||
node = self.record_to_html(cr, uid, 'record', {
|
||||
'record': self.pool['res.partner'].browse(cr, uid, ids[0], context=context)},
|
||||
options=options, context=context)
|
||||
@api.model
|
||||
def get_record_to_html(self, ids, options=None):
|
||||
node = self.record_to_html('record', {
|
||||
'record': self.env['res.partner'].browse(ids[0])},
|
||||
options=options)
|
||||
return node and node.__html__()
|
||||
|
||||
|
||||
@@ -193,15 +185,14 @@ class Date(orm.AbstractModel):
|
||||
_name = 'ir.qweb.field.date'
|
||||
_inherit = 'ir.qweb.field.date'
|
||||
|
||||
def attributes(self, cr, uid, field_name, record, options,
|
||||
source_element, g_att, t_att, qweb_context,
|
||||
context=None):
|
||||
attrs = super(Date, self).attributes(
|
||||
cr, uid, field_name, record, options, source_element, g_att, t_att,
|
||||
qweb_context, context=None)
|
||||
return itertools.chain(attrs, [('data-oe-original', record[field_name])])
|
||||
@api.model
|
||||
def attributes(self, record, field_name, options, qwebcontext):
|
||||
attrs = super(Date, self).attributes(record, field_name, options, qwebcontext)
|
||||
attrs['data-oe-original'] = record[field_name]
|
||||
return attrs
|
||||
|
||||
def from_html(self, cr, uid, model, field, element, context=None):
|
||||
@api.model
|
||||
def from_html(self, model, field, element):
|
||||
value = element.text_content().strip()
|
||||
if not value:
|
||||
return False
|
||||
@@ -214,9 +205,9 @@ class DateTime(orm.AbstractModel):
|
||||
_name = 'ir.qweb.field.datetime'
|
||||
_inherit = 'ir.qweb.field.datetime'
|
||||
|
||||
def attributes(self, cr, uid, field_name, record, options,
|
||||
source_element, g_att, t_att, qweb_context,
|
||||
context=None):
|
||||
@api.model
|
||||
def attributes(self, record, field_name, options, qwebcontext):
|
||||
attrs = super(DateTime, self).attributes(record, field_name, options, qwebcontext)
|
||||
value = record[field_name]
|
||||
if isinstance(value, basestring):
|
||||
value = datetime.datetime.strptime(
|
||||
@@ -224,19 +215,13 @@ class DateTime(orm.AbstractModel):
|
||||
if value:
|
||||
# convert from UTC (server timezone) to user timezone
|
||||
value = fields.datetime.context_timestamp(
|
||||
cr, uid, timestamp=value, context=context)
|
||||
self._cr, self._uid, timestamp=value, context=qwebcontext.context)
|
||||
value = value.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
attrs['data-oe-original'] = value
|
||||
return attrs
|
||||
|
||||
attrs = super(DateTime, self).attributes(
|
||||
cr, uid, field_name, record, options, source_element, g_att, t_att,
|
||||
qweb_context, context=None)
|
||||
return itertools.chain(attrs, [
|
||||
('data-oe-original', value)
|
||||
])
|
||||
|
||||
def from_html(self, cr, uid, model, field, element, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
@api.model
|
||||
def from_html(self, model, field, element):
|
||||
value = element.text_content().strip()
|
||||
if not value:
|
||||
return False
|
||||
@@ -245,8 +230,7 @@ class DateTime(orm.AbstractModel):
|
||||
dt = parser.parse(value)
|
||||
|
||||
# convert back from user's timezone to UTC
|
||||
tz_name = context.get('tz') \
|
||||
or self.pool['res.users'].read(cr, openerp.SUPERUSER_ID, uid, ['tz'], context=context)['tz']
|
||||
tz_name = self.env.context.get('tz') or self.env.user.tz
|
||||
if tz_name:
|
||||
try:
|
||||
user_tz = pytz.timezone(tz_name)
|
||||
@@ -268,7 +252,8 @@ class Text(orm.AbstractModel):
|
||||
_name = 'ir.qweb.field.text'
|
||||
_inherit = 'ir.qweb.field.text'
|
||||
|
||||
def from_html(self, cr, uid, model, field, element, context=None):
|
||||
@api.model
|
||||
def from_html(self, model, field, element):
|
||||
return html_to_text(element)
|
||||
|
||||
|
||||
@@ -276,10 +261,10 @@ class Selection(orm.AbstractModel):
|
||||
_name = 'ir.qweb.field.selection'
|
||||
_inherit = 'ir.qweb.field.selection'
|
||||
|
||||
def from_html(self, cr, uid, model, field, element, context=None):
|
||||
record = self.browse(cr, uid, [], context=context)
|
||||
@api.model
|
||||
def from_html(self, model, field, element):
|
||||
value = element.text_content().strip()
|
||||
selection = field.get_description(record.env)['selection']
|
||||
selection = field.get_description(self.env)['selection']
|
||||
for k, v in selection:
|
||||
if isinstance(v, str):
|
||||
v = ustr(v)
|
||||
@@ -294,7 +279,8 @@ class HTML(orm.AbstractModel):
|
||||
_name = 'ir.qweb.field.html'
|
||||
_inherit = 'ir.qweb.field.html'
|
||||
|
||||
def from_html(self, cr, uid, model, field, element, context=None):
|
||||
@api.model
|
||||
def from_html(self, model, field, element):
|
||||
content = []
|
||||
if element.text:
|
||||
content.append(element.text)
|
||||
@@ -313,20 +299,13 @@ class Image(orm.AbstractModel):
|
||||
_name = 'ir.qweb.field.image'
|
||||
_inherit = 'ir.qweb.field.image'
|
||||
|
||||
def to_html(self, cr, uid, field_name, record, options,
|
||||
source_element, t_att, g_att, qweb_context, context=None):
|
||||
assert source_element.tag != 'img',\
|
||||
@api.model
|
||||
def record_to_html(self, record, field_name, options, qwebcontext=None):
|
||||
assert options['tagName'] != 'img',\
|
||||
"Oddly enough, the root tag of an image field can not be img. " \
|
||||
"That is because the image goes into the tag, or it gets the " \
|
||||
"hose again."
|
||||
|
||||
return super(Image, self).to_html(
|
||||
cr, uid, field_name, record, options,
|
||||
source_element, t_att, g_att, qweb_context, context=context)
|
||||
|
||||
def record_to_html(self, cr, uid, field_name, record, options=None, context=None):
|
||||
if options is None:
|
||||
options = {}
|
||||
aclasses = ['img', 'img-responsive'] + options.get('class', '').split()
|
||||
classes = ' '.join(itertools.imap(escape, aclasses))
|
||||
|
||||
@@ -350,11 +329,12 @@ class Image(orm.AbstractModel):
|
||||
|
||||
img = '<img class="%s" src="%s" style="%s"%s/>' % \
|
||||
(classes, src, options.get('style', ''), ' alt="%s"' % alt if alt else '')
|
||||
return ir_qweb.HTMLSafe(img)
|
||||
return ir_qweb.unicodifier(img)
|
||||
|
||||
local_url_re = re.compile(r'^/(?P<module>[^]]+)/static/(?P<rest>.+)$')
|
||||
|
||||
def from_html(self, cr, uid, model, field, element, context=None):
|
||||
@api.model
|
||||
def from_html(self, model, field, element):
|
||||
url = element.find('img').get('src')
|
||||
|
||||
url_object = urlparse.urlsplit(url)
|
||||
@@ -370,7 +350,7 @@ class Image(orm.AbstractModel):
|
||||
model = query.get('model', fragments[3])
|
||||
oid = query.get('id', fragments[4].split('_')[0])
|
||||
field = query.get('field', fragments[5])
|
||||
item = self.pool[model].browse(cr, uid, int(oid), context=context)
|
||||
item = self.env[model].browse(int(oid))
|
||||
return item[field]
|
||||
|
||||
if self.local_url_re.match(url_object.path):
|
||||
@@ -432,8 +412,9 @@ class Monetary(orm.AbstractModel):
|
||||
_name = 'ir.qweb.field.monetary'
|
||||
_inherit = 'ir.qweb.field.monetary'
|
||||
|
||||
def from_html(self, cr, uid, model, field, element, context=None):
|
||||
lang = self.user_lang(cr, uid, context=context)
|
||||
@api.model
|
||||
def from_html(self, model, field, element):
|
||||
lang = self.user_lang()
|
||||
|
||||
value = element.find('span').text.strip()
|
||||
|
||||
@@ -445,15 +426,14 @@ class Duration(orm.AbstractModel):
|
||||
_name = 'ir.qweb.field.duration'
|
||||
_inherit = 'ir.qweb.field.duration'
|
||||
|
||||
def attributes(self, cr, uid, field_name, record, options,
|
||||
source_element, g_att, t_att, qweb_context,
|
||||
context=None):
|
||||
attrs = super(Duration, self).attributes(
|
||||
cr, uid, field_name, record, options, source_element, g_att, t_att,
|
||||
qweb_context, context=None)
|
||||
return itertools.chain(attrs, [('data-oe-original', record[field_name])])
|
||||
@api.model
|
||||
def attributes(self, record, field_name, options, qwebcontext):
|
||||
attrs = super(Duration, self).attributes(record, field_name, options, qwebcontext)
|
||||
attrs['data-oe-original'] = record[field_name]
|
||||
return attrs
|
||||
|
||||
def from_html(self, cr, uid, model, field, element, context=None):
|
||||
@api.model
|
||||
def from_html(self, model, field, element):
|
||||
value = element.text_content().strip()
|
||||
|
||||
# non-localized value
|
||||
|
||||
@@ -37,18 +37,17 @@ class view(osv.osv):
|
||||
def extract_embedded_fields(self, cr, uid, arch, context=None):
|
||||
return arch.xpath('//*[@data-oe-model != "ir.ui.view"]')
|
||||
|
||||
def save_embedded_field(self, cr, uid, el, context=None):
|
||||
Model = self.pool[el.get('data-oe-model')]
|
||||
@api.model
|
||||
def save_embedded_field(self, el):
|
||||
Model = self.env[el.get('data-oe-model')]
|
||||
field = el.get('data-oe-field')
|
||||
|
||||
converter = self.pool['ir.qweb'].get_converter_for(el.get('data-oe-type'))
|
||||
value = converter.from_html(cr, uid, Model, Model._fields[field], el)
|
||||
converter = self.env['ir.qweb'].get_field_for(el.get('data-oe-type'))
|
||||
value = converter.from_html(Model, Model._fields[field], el)
|
||||
|
||||
if value is not None:
|
||||
# TODO: batch writes?
|
||||
Model.write(cr, uid, [int(el.get('data-oe-id'))], {
|
||||
field: value
|
||||
}, context=context)
|
||||
Model.browse(int(el.get('data-oe-id'))).write({field: value})
|
||||
|
||||
def _pretty_arch(self, arch):
|
||||
# remove_blank_string does not seem to work on HTMLParser, and
|
||||
|
||||
@@ -21,7 +21,7 @@ class test_converter(orm.Model):
|
||||
(1, "réponse A"),
|
||||
(2, "réponse B"),
|
||||
(3, "réponse C"),
|
||||
(4, "réponse D"),
|
||||
(4, "réponse <D>"),
|
||||
]),
|
||||
'selection_str': fields.selection([
|
||||
('A', "Qu'il n'est pas arrivé à Toronto"),
|
||||
|
||||
@@ -213,8 +213,8 @@ var BuildingBlock = Widget.extend({
|
||||
}).on('click', function () {$(this).tooltip('hide');});
|
||||
|
||||
// t-snippet
|
||||
$html.find('[data-oe-type="snippet"][data-oe-name]').each(function () {
|
||||
var $div = $('<div/>').insertAfter(this).append(this).attr('name', $(this).data('oe-name'));
|
||||
$html.find('[data-oe-type="snippet"]').each(function () {
|
||||
$(this).children().attr('data-oe-type', "snippet").attr('data-oe-thumbnail', $(this).data('oe-thumbnail'));
|
||||
});
|
||||
// end
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import werkzeug.utils
|
||||
|
||||
import openerp
|
||||
from openerp.addons.base import ir
|
||||
from openerp.addons.base.ir import ir_qweb
|
||||
from odoo.exceptions import QWebException
|
||||
from openerp.addons.website.models.website import slug, url_for, _UNSLUG_RE
|
||||
from openerp.http import request
|
||||
from openerp.tools import config
|
||||
@@ -247,7 +247,7 @@ class ir_http(orm.AbstractModel):
|
||||
# if parent excplicitely returns a plain response, then we don't touch it
|
||||
return response
|
||||
except Exception, e:
|
||||
if 'werkzeug' in config['dev_mode'] and (not isinstance(exception, ir_qweb.QWebException) or not exception.qweb.get('cause')):
|
||||
if 'werkzeug' in config['dev_mode'] and (not isinstance(exception, QWebException) or not exception.qweb.get('cause')):
|
||||
raise
|
||||
exception = e
|
||||
|
||||
@@ -267,7 +267,7 @@ class ir_http(orm.AbstractModel):
|
||||
if isinstance(exception, openerp.exceptions.AccessError):
|
||||
code = 403
|
||||
|
||||
if isinstance(exception, ir_qweb.QWebException):
|
||||
if isinstance(exception, QWebException):
|
||||
values.update(qweb_exception=exception)
|
||||
if isinstance(exception.qweb.get('cause'), openerp.exceptions.AccessError):
|
||||
code = 403
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from openerp.addons.web.http import request
|
||||
from openerp.osv import orm
|
||||
import ast
|
||||
from openerp.addons.base.ir.ir_qweb import utils
|
||||
|
||||
|
||||
class QWeb(orm.AbstractModel):
|
||||
@@ -26,24 +28,46 @@ class QWeb(orm.AbstractModel):
|
||||
'style',
|
||||
]
|
||||
|
||||
def render_attribute(self, element, name, value, qwebcontext):
|
||||
def _website_build_attribute(self, tagName, name, value, qwebcontext):
|
||||
context = qwebcontext.context or {}
|
||||
if not context.get('rendering_bundle'):
|
||||
if name == self.URL_ATTRS.get(element.tag) and qwebcontext.get('url_for'):
|
||||
value = qwebcontext.get('url_for')(value)
|
||||
elif request and getattr(request, 'website', None) and request.website.cdn_activated and (name == self.URL_ATTRS.get(element.tag) or name == self.CDN_TRIGGERS.get(element.tag)):
|
||||
value = request.website.get_cdn_url(value)
|
||||
return super(QWeb, self).render_attribute(element, name, value, qwebcontext)
|
||||
if name == self.URL_ATTRS.get(tagName) and qwebcontext.get('url_for'):
|
||||
return qwebcontext.get('url_for')(value or '')
|
||||
elif request and request.website and request.website.cdn_activated and (name == self.URL_ATTRS.get(tagName) or name == self.CDN_TRIGGERS.get(tagName)):
|
||||
return request.website.get_cdn_url(value or '')
|
||||
return value
|
||||
|
||||
def render_text(self, text, element, qwebcontext):
|
||||
compress = request and not request.debug and getattr(request, 'website', None) and request.website.compress_html
|
||||
if compress and element.tag not in self.PRESERVE_WHITESPACE:
|
||||
text = self.re_remove_spaces.sub(' ', text)
|
||||
return super(QWeb, self).render_text(text, element, qwebcontext)
|
||||
def _wrap_build_attributes(self, el, items):
|
||||
url_att = self.URL_ATTRS.get(el.tag)
|
||||
cdn_att = self.CDN_TRIGGERS.get(el.tag)
|
||||
for item in items:
|
||||
if isinstance(item, tuple) and (item[0] == url_att or item[0] == cdn_att):
|
||||
items[items.index(item)] = (item[0], ast.Call(
|
||||
func=ast.Attribute(
|
||||
value=ast.Name(id='self', ctx=ast.Load()),
|
||||
attr='_website_build_attribute',
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[
|
||||
ast.Str(el.tag),
|
||||
ast.Str(item[0]),
|
||||
item[1],
|
||||
ast.Name(id='qwebcontext', ctx=ast.Load()),
|
||||
], keywords=[],
|
||||
starargs=None, kwargs=None
|
||||
))
|
||||
return items
|
||||
|
||||
def render_tail(self, tail, element, qwebcontext):
|
||||
compress = request and not request.debug and getattr(request, 'website', None) and request.website.compress_html
|
||||
if compress and element.getparent().tag not in self.PRESERVE_WHITESPACE:
|
||||
# No need to recurse because those tags children are not html5 parser friendly
|
||||
tail = self.re_remove_spaces.sub(' ', tail.rstrip())
|
||||
return super(QWeb, self).render_tail(tail, element, qwebcontext)
|
||||
def _serialize_static_attributes(self, el):
|
||||
items = super(QWeb, self)._serialize_static_attributes(el)
|
||||
return self._wrap_build_attributes(el, items)
|
||||
|
||||
def _compile_dynamic_attributes(self, el):
|
||||
items = super(QWeb, self)._compile_dynamic_attributes(el)
|
||||
return self._wrap_build_attributes(el, items)
|
||||
|
||||
def _compile_dynamic_att(self, tagName, atts, qwebcontext):
|
||||
atts = super(QWeb, self)._compile_dynamic_att(tagName, atts, qwebcontext)
|
||||
for name, value in atts.iteritems():
|
||||
atts[name] = self.build_attribute(tagName, name, value, qwebcontext)
|
||||
return atts
|
||||
|
||||
@@ -139,7 +139,7 @@
|
||||
<span t-field="blog_post.author_avatar" class="o_not_editable" t-field-options='{"widget": "image", "class": "img-circle pull-right mt16 media-object"}' />
|
||||
</t>
|
||||
<a t-attf-href="/blog/#{ slug(blog_post.blog_id) }/post/#{ slug(blog_post) }">
|
||||
<h2 t-field="blog_post.name" class="mb4"><t t-if="not blog_post.name">Untitled Post</t></h2>
|
||||
<h2 t-field="blog_post.name" class="mb4">Untitled Post</h2>
|
||||
</a>
|
||||
<div class="text-muted">
|
||||
<h4 t-field="blog_post.subtitle" class="mb4 mt4"></h4>
|
||||
|
||||
@@ -10,7 +10,7 @@ from openerp.http import request
|
||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from openerp.tools.translate import _
|
||||
from openerp.exceptions import ValidationError
|
||||
from openerp.addons.base.ir.ir_qweb import nl2br
|
||||
from openerp.addons.base.ir.ir_qweb.fields import nl2br
|
||||
|
||||
class WebsiteForm(http.Controller):
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
<div class="col-sm-9">
|
||||
<span name="form_amount" class="form-control" t-esc="amount"/>
|
||||
<!--
|
||||
<span name="form_amount" class="form-control" t-esc="amount" t-esc-options='{"widget": "monetary", "display_currency": "currency"}'/>
|
||||
<span name="form_amount" class="form-control" t-esc="format(amount, 'monetary', currency)"/>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
@@ -149,7 +149,7 @@
|
||||
<div class="form-group">
|
||||
<label for="form_amount" class="col-sm-3 control-label">Amount</label>
|
||||
<div class="col-sm-9">
|
||||
<span name="form_amount" class="form-control" t-esc="tx.amount" t-esc-options='{"widget": "monetary", "display_currency": "tx.currency_id"}'/>
|
||||
<span name="form_amount" class="form-control" t-esc="format(tx.amount, 'monetary', tx.currency_id)"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
<td class="text-right" style="padding-bottom: 32px"><strong>Subtotal:</strong></td>
|
||||
<td class="text-right" style="padding-bottom: 32px">
|
||||
<t t-set="subtotal" t-value="sum(line.price_subtotal for line in layout_category['lines'])"/>
|
||||
<strong data-id="total_amount" t-esc="subtotal" t-esc-options='{"widget": "monetary","display_currency": "quotation.pricelist_id.currency_id"}'/>
|
||||
<strong data-id="total_amount" t-esc="format(subtotal, 'monetary', quotation.pricelist_id.currency_id)"/>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
@@ -615,7 +615,6 @@ class WebsiteSale(http.Controller):
|
||||
|
||||
@http.route(['/shop/confirm_order'], type='http', auth="public", website=True)
|
||||
def confirm_order(self, **post):
|
||||
|
||||
order = request.website.sale_get_order()
|
||||
if not order:
|
||||
return request.redirect("/shop")
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from openerp.tools import safe_eval, html_escape as escape
|
||||
|
||||
from . import fields
|
||||
from . import assetsbundle
|
||||
from . import utils
|
||||
|
||||
from .assetsbundle import AssetsBundle
|
||||
from .utils import QWebContext, unicodifier
|
||||
from .qweb import QWeb
|
||||
@@ -0,0 +1,615 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import re
|
||||
import hashlib
|
||||
import itertools
|
||||
import json
|
||||
import textwrap
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from subprocess import Popen, PIPE
|
||||
from odoo import fields, tools
|
||||
from openerp.http import request
|
||||
from odoo.modules.module import get_resource_path
|
||||
import psycopg2
|
||||
import werkzeug
|
||||
from openerp.tools import func, misc
|
||||
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
MAX_CSS_RULES = 4095
|
||||
|
||||
|
||||
def rjsmin(script):
|
||||
""" Minify js with a clever regex.
|
||||
Taken from http://opensource.perlig.de/rjsmin
|
||||
Apache License, Version 2.0 """
|
||||
def subber(match):
|
||||
""" Substitution callback """
|
||||
groups = match.groups()
|
||||
return (
|
||||
groups[0] or
|
||||
groups[1] or
|
||||
groups[2] or
|
||||
groups[3] or
|
||||
(groups[4] and '\n') or
|
||||
(groups[5] and ' ') or
|
||||
(groups[6] and ' ') or
|
||||
(groups[7] and ' ') or
|
||||
''
|
||||
)
|
||||
|
||||
result = re.sub(
|
||||
r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?'
|
||||
r'\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|'
|
||||
r'\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?:(?<=[(,=:\[!&|?{};\r\n]'
|
||||
r')(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'
|
||||
r'))*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*'
|
||||
r'(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]*'
|
||||
r'))|(?:(?<=[\000-#%-,./:-@\[-^`{-~-]return)(?:[\000-\011\013\014\01'
|
||||
r'6-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*((?:/(?![\r\n/*])[^/'
|
||||
r'\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]'
|
||||
r'*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]*))|(?<=[^\000-!#%&(*,./'
|
||||
r':-@\[\\^`{|~])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/'
|
||||
r'*][^*]*\*+)*/))*(?:((?:(?://[^\r\n]*)?[\r\n]))(?:[\000-\011\013\01'
|
||||
r'4\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040"#'
|
||||
r'%-\047)*,./:-@\\-^`|-~])|(?<=[^\000-#%-,./:-@\[-^`{-~-])((?:[\000-'
|
||||
r'\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=[^'
|
||||
r'\000-#%-,./:-@\[-^`{-~-])|(?<=\+)((?:[\000-\011\013\014\016-\040]|'
|
||||
r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=\+)|(?<=-)((?:[\000-\011\0'
|
||||
r'13\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=-)|(?:[\0'
|
||||
r'00-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))+|(?:'
|
||||
r'(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*'
|
||||
r']*\*+(?:[^/*][^*]*\*+)*/))*)+', subber, '\n%s\n' % script
|
||||
).strip()
|
||||
return result
|
||||
|
||||
|
||||
class AssetError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AssetNotFound(AssetError):
|
||||
pass
|
||||
|
||||
|
||||
class AssetsBundle(object):
|
||||
rx_css_import = re.compile("(@import[^;{]+;?)", re.M)
|
||||
rx_preprocess_imports = re.compile("""(@import\s?['"]([^'"]+)['"](;?))""")
|
||||
rx_css_split = re.compile("\/\*\! ([a-f0-9-]+) \*\/")
|
||||
|
||||
def __init__(self, name, files, remains, env=None):
|
||||
self.name = name
|
||||
self.env = request.env if env is None else env
|
||||
self.max_css_rules = self.env.context.get('max_css_rules', MAX_CSS_RULES)
|
||||
self.javascripts = []
|
||||
self.stylesheets = []
|
||||
self.css_errors = []
|
||||
self.remains = []
|
||||
self._checksum = None
|
||||
self.files = files
|
||||
self.remains = remains
|
||||
for f in files:
|
||||
if f['atype'] == 'text/sass':
|
||||
self.stylesheets.append(SassStylesheetAsset(self, url=f['url'], inline=f['content'], media=f['media']))
|
||||
elif f['atype'] == 'text/less':
|
||||
self.stylesheets.append(LessStylesheetAsset(self, url=f['url'], inline=f['content'], media=f['media']))
|
||||
elif f['atype'] == 'text/css':
|
||||
self.stylesheets.append(StylesheetAsset(self, url=f['url'], inline=f['content'], media=f['media']))
|
||||
elif f['atype'] == 'text/javascript':
|
||||
self.javascripts.append(JavascriptAsset(self, url=f['url'], inline=f['content']))
|
||||
|
||||
def to_html(self, sep=None, css=True, js=True, debug=False, async=False, qwebcontext=None):
|
||||
url_for = qwebcontext and qwebcontext.get('url_for', lambda url: url) or (lambda url: url)
|
||||
if sep is None:
|
||||
sep = '\n '
|
||||
response = []
|
||||
if debug == 'assets':
|
||||
if css and self.stylesheets:
|
||||
if not self.is_css_preprocessed():
|
||||
self.preprocess_css(debug=debug)
|
||||
if self.css_errors:
|
||||
msg = '\n'.join(self.css_errors)
|
||||
self.stylesheets.append(StylesheetAsset(self, inline=self.css_message(msg)))
|
||||
for style in self.stylesheets:
|
||||
response.append(style.to_html())
|
||||
if js:
|
||||
for jscript in self.javascripts:
|
||||
response.append(jscript.to_html())
|
||||
else:
|
||||
if css and self.stylesheets:
|
||||
css_attachments = self.css()
|
||||
if not self.css_errors:
|
||||
for attachment in css_attachments:
|
||||
response.append('<link href="%s" rel="stylesheet"/>' % url_for(attachment.url))
|
||||
else:
|
||||
msg = '\n'.join(self.css_errors)
|
||||
self.stylesheets.append(StylesheetAsset(self, inline=self.css_message(msg)))
|
||||
for style in self.stylesheets:
|
||||
response.append(style.to_html())
|
||||
if js and self.javascripts:
|
||||
response.append('<script %s type="text/javascript" src="%s"></script>' % (async and 'async="async"' or '', url_for(self.js().url)))
|
||||
response.extend(self.remains)
|
||||
|
||||
return sep + sep.join(response)
|
||||
|
||||
@func.lazy_property
|
||||
def last_modified(self):
|
||||
"""Returns last modified date of linked files"""
|
||||
return max(itertools.chain(
|
||||
(asset.last_modified for asset in self.javascripts),
|
||||
(asset.last_modified for asset in self.stylesheets),
|
||||
))
|
||||
|
||||
@func.lazy_property
|
||||
def version(self):
|
||||
return self.checksum[0:7]
|
||||
|
||||
@func.lazy_property
|
||||
def checksum(self):
|
||||
"""
|
||||
Not really a full checksum.
|
||||
We compute a SHA1 on the rendered bundle + max linked files last_modified date
|
||||
"""
|
||||
check = json.dumps(self.files) + ",".join(self.remains) + str(self.last_modified)
|
||||
return hashlib.sha1(check).hexdigest()
|
||||
|
||||
def clean_attachments(self, type):
|
||||
""" Takes care of deleting any outdated ir.attachment records associated to a bundle before
|
||||
saving a fresh one.
|
||||
|
||||
When `type` is css we need to check that we are deleting a different version (and not *any*
|
||||
version) because css may be paginated and, therefore, may produce multiple attachments for
|
||||
the same bundle's version.
|
||||
|
||||
When `type` is js we need to check that we are deleting a different version (and not *any*
|
||||
version) because, as one of the creates in `save_attachment` can trigger a rollback, the
|
||||
call to `clean_attachments ` is made at the end of the method in order to avoid the rollback
|
||||
of an ir.attachment unlink (because we cannot rollback a removal on the filestore), thus we
|
||||
must exclude the current bundle.
|
||||
"""
|
||||
ira = self.env['ir.attachment']
|
||||
domain = [
|
||||
('url', '=like', '/web/content/%-%/{0}%.{1}'.format(self.name, type)), # The wilcards are id, version and pagination number (if any)
|
||||
'!', ('url', '=like', '/web/content/%-{}/%'.format(self.version))
|
||||
]
|
||||
return ira.sudo().search(domain).unlink()
|
||||
|
||||
def get_attachments(self, type, inc=None):
|
||||
ira = self.env['ir.attachment']
|
||||
domain = [('url', '=like', '/web/content/%%-%s/%s%s.%s' % (self.version, self.name, ('%%' if inc is None else '.%s' % inc), type))]
|
||||
return ira.sudo().search(domain, order='name asc')
|
||||
|
||||
def save_attachment(self, type, content, inc=None):
|
||||
ira = self.env['ir.attachment']
|
||||
|
||||
fname = '%s%s.%s' % (self.name, ('' if inc is None else '.%s' % inc), type)
|
||||
values = {
|
||||
'name': "/web/content/%s" % type,
|
||||
'datas_fname': fname,
|
||||
'res_model': 'ir.ui.view',
|
||||
'res_id': False,
|
||||
'type': 'binary',
|
||||
'public': True,
|
||||
'datas': content.encode('utf8').encode('base64'),
|
||||
}
|
||||
attachment = ira.sudo().create(values)
|
||||
|
||||
url = '/web/content/%s-%s/%s' % (attachment.id, self.version, fname)
|
||||
values = {
|
||||
'name': url,
|
||||
'url': url,
|
||||
}
|
||||
attachment.write(values)
|
||||
|
||||
if self.env.context.get('commit_assetsbundle') is True:
|
||||
self.env.cr.commit()
|
||||
|
||||
self.clean_attachments(type)
|
||||
|
||||
return attachment
|
||||
|
||||
def js(self):
|
||||
attachments = self.get_attachments('js')
|
||||
if not attachments:
|
||||
content = ';\n'.join(asset.minify() for asset in self.javascripts)
|
||||
return self.save_attachment('js', content)
|
||||
return attachments[0]
|
||||
|
||||
def css(self):
|
||||
attachments = self.get_attachments('css')
|
||||
if not attachments:
|
||||
# get css content
|
||||
css = self.preprocess_css()
|
||||
if self.css_errors:
|
||||
return
|
||||
|
||||
# move up all @import rules to the top
|
||||
matches = []
|
||||
css = re.sub(self.rx_css_import, lambda matchobj: matches.append(matchobj.group(0)) and '', css)
|
||||
matches.append(css)
|
||||
css = u'\n'.join(matches)
|
||||
|
||||
# split for browser max file size and browser max expression
|
||||
re_rules = '([^{]+\{(?:[^{}]|\{[^{}]*\})*\})'
|
||||
re_selectors = '()(?:\s*@media\s*[^{]*\{)?(?:\s*(?:[^,{]*(?:,|\{(?:[^}]*\}))))'
|
||||
page = []
|
||||
pages = [page]
|
||||
page_selectors = 0
|
||||
for rule in re.findall(re_rules, css):
|
||||
selectors = len(re.findall(re_selectors, rule))
|
||||
if page_selectors + selectors <= self.max_css_rules:
|
||||
page_selectors += selectors
|
||||
page.append(rule)
|
||||
else:
|
||||
pages.append([rule])
|
||||
page = pages[-1]
|
||||
page_selectors = selectors
|
||||
for idx, page in enumerate(pages):
|
||||
self.save_attachment("css", ' '.join(page), inc=idx)
|
||||
attachments = self.get_attachments('css')
|
||||
return attachments
|
||||
|
||||
def css_message(self, message):
|
||||
# '\A' == css content carriage return
|
||||
message = message.replace('\n', '\\A ').replace('"', '\\"')
|
||||
return """
|
||||
body:before {
|
||||
background: #ffc;
|
||||
width: 100%%;
|
||||
font-size: 14px;
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
content: "%s";
|
||||
}
|
||||
""" % message
|
||||
|
||||
def is_css_preprocessed(self):
|
||||
preprocessed = True
|
||||
for atype in (SassStylesheetAsset, LessStylesheetAsset):
|
||||
outdated = False
|
||||
assets = dict((asset.html_url, asset) for asset in self.stylesheets if isinstance(asset, atype))
|
||||
if assets:
|
||||
assets_domain = [('url', 'in', assets.keys())]
|
||||
attachments = self.env['ir.attachment'].sudo().search(assets_domain)
|
||||
for attachment in attachments:
|
||||
asset = assets[attachment.url]
|
||||
if asset.last_modified > fields.Datetime.from_string(attachment['__last_update']):
|
||||
outdated = True
|
||||
break
|
||||
if asset._content is None:
|
||||
asset._content = attachment.datas and attachment.datas.decode('base64').decode('utf8') or ''
|
||||
if not asset._content and attachment.file_size > 0:
|
||||
asset._content = None # file missing, force recompile
|
||||
|
||||
if any(asset._content is None for asset in assets.itervalues()):
|
||||
outdated = True
|
||||
|
||||
if outdated:
|
||||
if attachments:
|
||||
attachments.unlink()
|
||||
preprocessed = False
|
||||
|
||||
return preprocessed
|
||||
|
||||
def preprocess_css(self, debug=False):
|
||||
"""
|
||||
Checks if the bundle contains any sass/less content, then compiles it to css.
|
||||
Returns the bundle's flat css.
|
||||
"""
|
||||
for atype in (SassStylesheetAsset, LessStylesheetAsset):
|
||||
assets = [asset for asset in self.stylesheets if isinstance(asset, atype)]
|
||||
if assets:
|
||||
cmd = assets[0].get_command()
|
||||
source = '\n'.join([asset.get_source() for asset in assets])
|
||||
compiled = self.compile_css(cmd, source)
|
||||
|
||||
fragments = self.rx_css_split.split(compiled)
|
||||
at_rules = fragments.pop(0)
|
||||
if at_rules:
|
||||
# Sass and less moves @at-rules to the top in order to stay css 2.1 compatible
|
||||
self.stylesheets.insert(0, StylesheetAsset(self, inline=at_rules))
|
||||
while fragments:
|
||||
asset_id = fragments.pop(0)
|
||||
asset = next(asset for asset in self.stylesheets if asset.id == asset_id)
|
||||
asset._content = fragments.pop(0)
|
||||
|
||||
if debug:
|
||||
try:
|
||||
fname = os.path.basename(asset.url)
|
||||
url = asset.html_url
|
||||
with self.env.cr.savepoint():
|
||||
self.env['ir.attachment'].sudo().create(dict(
|
||||
datas=asset.content.encode('utf8').encode('base64'),
|
||||
mimetype='text/css',
|
||||
type='binary',
|
||||
name=url,
|
||||
url=url,
|
||||
datas_fname=fname,
|
||||
res_model=False,
|
||||
res_id=False,
|
||||
))
|
||||
|
||||
if self.env.context.get('commit_assetsbundle') is True:
|
||||
self.env.cr.commit()
|
||||
except psycopg2.Error:
|
||||
pass
|
||||
|
||||
return '\n'.join(asset.minify() for asset in self.stylesheets)
|
||||
|
||||
def compile_css(self, cmd, source):
|
||||
"""Sanitizes @import rules, remove duplicates @import rules, then compile"""
|
||||
imports = []
|
||||
|
||||
def sanitize(matchobj):
|
||||
ref = matchobj.group(2)
|
||||
line = '@import "%s"%s' % (ref, matchobj.group(3))
|
||||
if '.' not in ref and line not in imports and not ref.startswith(('.', '/', '~')):
|
||||
imports.append(line)
|
||||
return line
|
||||
msg = "Local import '%s' is forbidden for security reasons." % ref
|
||||
_logger.warning(msg)
|
||||
self.css_errors.append(msg)
|
||||
return ''
|
||||
source = re.sub(self.rx_preprocess_imports, sanitize, source)
|
||||
|
||||
try:
|
||||
compiler = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
||||
except Exception:
|
||||
msg = "Could not execute command %r" % cmd[0]
|
||||
_logger.error(msg)
|
||||
self.css_errors.append(msg)
|
||||
return ''
|
||||
result = compiler.communicate(input=source.encode('utf-8'))
|
||||
if compiler.returncode:
|
||||
error = self.get_preprocessor_error(''.join(result), source=source)
|
||||
_logger.warning(error)
|
||||
self.css_errors.append(error)
|
||||
return ''
|
||||
compiled = result[0].strip().decode('utf8')
|
||||
return compiled
|
||||
|
||||
def get_preprocessor_error(self, stderr, source=None):
|
||||
"""Improve and remove sensitive information from sass/less compilator error messages"""
|
||||
error = stderr.split('Load paths')[0].replace(' Use --trace for backtrace.', '')
|
||||
if 'Cannot load compass' in error:
|
||||
error += "Maybe you should install the compass gem using this extra argument:\n\n" \
|
||||
" $ sudo gem install compass --pre\n"
|
||||
error += "This error occured while compiling the bundle '%s' containing:" % self.name
|
||||
for asset in self.stylesheets:
|
||||
if isinstance(asset, PreprocessedCSS):
|
||||
error += '\n - %s' % (asset.url if asset.url else '<inline sass>')
|
||||
return error
|
||||
|
||||
|
||||
class WebAsset(object):
|
||||
html_url_format = '%s'
|
||||
|
||||
def __init__(self, bundle, inline=None, url=None):
|
||||
self.id = str(uuid.uuid4())
|
||||
self.bundle = bundle
|
||||
self.inline = inline
|
||||
self.url = url
|
||||
self.html_url_args = url
|
||||
self.env = bundle.env
|
||||
self._content = None
|
||||
self._filename = None
|
||||
self._ir_attach = None
|
||||
name = '<inline asset>' if inline else url
|
||||
self.name = "%s defined in bundle '%s'" % (name, bundle.name)
|
||||
if not inline and not url:
|
||||
raise Exception("An asset should either be inlined or url linked, defined in bundle '%s'" % bundle.name)
|
||||
|
||||
@property
|
||||
def html_url(self):
|
||||
return self.html_url_format % self.html_url_args
|
||||
|
||||
def stat(self):
|
||||
if not (self.inline or self._filename or self._ir_attach):
|
||||
path = filter(None, self.url.split('/'))
|
||||
self._filename = get_resource_path(*path)
|
||||
if self._filename:
|
||||
return
|
||||
try:
|
||||
# Test url against ir.attachments
|
||||
fields = ['__last_update', 'datas', 'mimetype']
|
||||
domain = [('type', '=', 'binary'), ('url', '=', self.url)]
|
||||
attach = self.env['ir.attachment'].sudo().search_read(domain, fields)
|
||||
self._ir_attach = attach[0]
|
||||
except Exception:
|
||||
raise AssetNotFound("Could not find %s" % self.name)
|
||||
|
||||
def to_html(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@func.lazy_property
|
||||
def last_modified(self):
|
||||
try:
|
||||
self.stat()
|
||||
if self._filename:
|
||||
return datetime.fromtimestamp(os.path.getmtime(self._filename))
|
||||
elif self._ir_attach:
|
||||
server_format = tools.DEFAULT_SERVER_DATETIME_FORMAT
|
||||
last_update = self._ir_attach['__last_update']
|
||||
try:
|
||||
return datetime.strptime(last_update, server_format + '.%f')
|
||||
except ValueError:
|
||||
return datetime.strptime(last_update, server_format)
|
||||
except Exception:
|
||||
pass
|
||||
return datetime(1970, 1, 1)
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
if self._content is None:
|
||||
self._content = self.inline or self._fetch_content()
|
||||
return self._content
|
||||
|
||||
def _fetch_content(self):
|
||||
""" Fetch content from file or database"""
|
||||
try:
|
||||
self.stat()
|
||||
if self._filename:
|
||||
with open(self._filename, 'rb') as fp:
|
||||
return fp.read().decode('utf-8')
|
||||
else:
|
||||
return self._ir_attach['datas'].decode('base64')
|
||||
except UnicodeDecodeError:
|
||||
raise AssetError('%s is not utf-8 encoded.' % self.name)
|
||||
except IOError:
|
||||
raise AssetNotFound('File %s does not exist.' % self.name)
|
||||
except:
|
||||
raise AssetError('Could not get content for %s.' % self.name)
|
||||
|
||||
def minify(self):
|
||||
return self.content
|
||||
|
||||
def with_header(self, content=None):
|
||||
if content is None:
|
||||
content = self.content
|
||||
return '\n/* %s */\n%s' % (self.name, content)
|
||||
|
||||
|
||||
class JavascriptAsset(WebAsset):
|
||||
def minify(self):
|
||||
return self.with_header(rjsmin(self.content))
|
||||
|
||||
def _fetch_content(self):
|
||||
try:
|
||||
return super(JavascriptAsset, self)._fetch_content()
|
||||
except AssetError, e:
|
||||
return "console.error(%s);" % json.dumps(e.message)
|
||||
|
||||
def to_html(self):
|
||||
if self.url:
|
||||
return '<script type="text/javascript" src="%s"></script>' % (self.html_url)
|
||||
else:
|
||||
return '<script type="text/javascript" charset="utf-8">%s</script>' % self.with_header()
|
||||
|
||||
|
||||
class StylesheetAsset(WebAsset):
|
||||
rx_import = re.compile(r"""@import\s+('|")(?!'|"|/|https?://)""", re.U)
|
||||
rx_url = re.compile(r"""url\s*\(\s*('|"|)(?!'|"|/|https?://|data:)""", re.U)
|
||||
rx_sourceMap = re.compile(r'(/\*# sourceMappingURL=.*)', re.U)
|
||||
rx_charset = re.compile(r'(@charset "[^"]+";)', re.U)
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
self.media = kw.pop('media', None)
|
||||
super(StylesheetAsset, self).__init__(*args, **kw)
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
content = super(StylesheetAsset, self).content
|
||||
if self.media:
|
||||
content = '@media %s { %s }' % (self.media, content)
|
||||
return content
|
||||
|
||||
def _fetch_content(self):
|
||||
try:
|
||||
content = super(StylesheetAsset, self)._fetch_content()
|
||||
web_dir = os.path.dirname(self.url)
|
||||
|
||||
if self.rx_import:
|
||||
content = self.rx_import.sub(
|
||||
r"""@import \1%s/""" % (web_dir,),
|
||||
content,
|
||||
)
|
||||
|
||||
if self.rx_url:
|
||||
content = self.rx_url.sub(
|
||||
r"url(\1%s/" % (web_dir,),
|
||||
content,
|
||||
)
|
||||
|
||||
if self.rx_charset:
|
||||
# remove charset declarations, we only support utf-8
|
||||
content = self.rx_charset.sub('', content)
|
||||
|
||||
return content
|
||||
except AssetError, e:
|
||||
self.bundle.css_errors.append(e.message)
|
||||
return ''
|
||||
|
||||
def minify(self):
|
||||
# remove existing sourcemaps, make no sense after re-mini
|
||||
content = self.rx_sourceMap.sub('', self.content)
|
||||
# comments
|
||||
content = re.sub(r'/\*.*?\*/', '', content, flags=re.S)
|
||||
# space
|
||||
content = re.sub(r'\s+', ' ', content)
|
||||
content = re.sub(r' *([{}]) *', r'\1', content)
|
||||
return self.with_header(content)
|
||||
|
||||
def to_html(self):
|
||||
media = (' media="%s"' % werkzeug.utils.escape(self.media)) if self.media else ''
|
||||
if self.url:
|
||||
href = self.html_url
|
||||
return '<link rel="stylesheet" href="%s" type="text/css"%s/>' % (href, media)
|
||||
else:
|
||||
return '<style type="text/css"%s>%s</style>' % (media, self.with_header())
|
||||
|
||||
|
||||
class PreprocessedCSS(StylesheetAsset):
|
||||
rx_import = None
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(PreprocessedCSS, self).__init__(*args, **kw)
|
||||
self.html_url_format = '%%s/%s/%%s.css' % self.bundle.name
|
||||
self.html_url_args = tuple(self.url.rsplit('/', 1))
|
||||
|
||||
def minify(self):
|
||||
return self.with_header()
|
||||
|
||||
def get_source(self):
|
||||
content = self.inline or self._fetch_content()
|
||||
return "/*! %s */\n%s" % (self.id, content)
|
||||
|
||||
def get_command(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class SassStylesheetAsset(PreprocessedCSS):
|
||||
rx_indent = re.compile(r'^( +|\t+)', re.M)
|
||||
indent = None
|
||||
reindent = ' '
|
||||
|
||||
def get_source(self):
|
||||
content = textwrap.dedent(self.inline or self._fetch_content())
|
||||
|
||||
def fix_indent(m):
|
||||
# Indentation normalization
|
||||
ind = m.group()
|
||||
if self.indent is None:
|
||||
self.indent = ind
|
||||
if self.indent == self.reindent:
|
||||
# Don't reindent the file if identation is the final one (reindent)
|
||||
raise StopIteration()
|
||||
return ind.replace(self.indent, self.reindent)
|
||||
|
||||
try:
|
||||
content = self.rx_indent.sub(fix_indent, content)
|
||||
except StopIteration:
|
||||
pass
|
||||
return "/*! %s */\n%s" % (self.id, content)
|
||||
|
||||
def get_command(self):
|
||||
try:
|
||||
sass = misc.find_in_path('sass')
|
||||
except IOError:
|
||||
sass = 'sass'
|
||||
return [sass, '--stdin', '-t', 'compressed', '--unix-newlines', '--compass',
|
||||
'-r', 'bootstrap-sass']
|
||||
|
||||
|
||||
class LessStylesheetAsset(PreprocessedCSS):
|
||||
def get_command(self):
|
||||
try:
|
||||
if os.name == 'nt':
|
||||
lessc = misc.find_in_path('lessc.cmd')
|
||||
else:
|
||||
lessc = misc.find_in_path('lessc')
|
||||
except IOError:
|
||||
lessc = 'lessc'
|
||||
lesspath = get_resource_path('web', 'static', 'lib', 'bootstrap', 'less')
|
||||
return [lessc, '-', '--clean-css', '--no-js', '--no-color', '--include-path=%s' % lesspath]
|
||||
@@ -0,0 +1,463 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
from collections import OrderedDict
|
||||
from odoo import api, fields, models, _
|
||||
from PIL import Image
|
||||
from cStringIO import StringIO
|
||||
import babel
|
||||
from openerp.tools import html_escape as escape, posix_to_ldml
|
||||
from .utils import QWebContext, eval_expr, unicodifier
|
||||
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def nl2br(string):
|
||||
""" Converts newlines to HTML linebreaks in ``string``. returns
|
||||
the unicode result
|
||||
|
||||
:param str string:
|
||||
:rtype: unicode
|
||||
"""
|
||||
return unicodifier(string).replace(u'\n', u'<br>\n')
|
||||
|
||||
def html_escape(string, options):
|
||||
""" Automatically escapes content unless options['html-escape']
|
||||
is set to False
|
||||
|
||||
:param str string:
|
||||
:param dict options:
|
||||
"""
|
||||
return escape(string) if not options or options.get('html-escape', True) else string
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# QWeb Fields converters
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
class FieldConverter(models.AbstractModel):
|
||||
""" Used to convert a t-field specification into an output HTML field.
|
||||
|
||||
:meth:`~.to_html` is the entry point of this conversion from QWeb, it:
|
||||
|
||||
* converts the record value to html using :meth:`~.record_to_html`
|
||||
* generates the metadata attributes (``data-oe-``) to set on the root
|
||||
result node
|
||||
* generates the root result node itself through :meth:`~.render_element`
|
||||
"""
|
||||
_name = 'ir.qweb.field'
|
||||
|
||||
@api.model
|
||||
def attributes(self, record, field_name, options, qwebcontext=None):
|
||||
""" attributes(record, field_name, field, options, qwebcontext)
|
||||
|
||||
Generates the metadata attributes (prefixed by ``data-oe-``) for the
|
||||
root node of the field conversion.
|
||||
|
||||
The default attributes are:
|
||||
|
||||
* ``model``, the name of the record's model
|
||||
* ``id`` the id of the record to which the field belongs
|
||||
* ``type`` the logical field type (widget, may not match the field's
|
||||
``type``, may not be any Field subclass name)
|
||||
* ``translate``, a boolean flag (``0`` or ``1``) denoting whether the
|
||||
field is translatable
|
||||
* ``readonly``, has this attribute if the field is readonly
|
||||
* ``expression``, the original expression
|
||||
|
||||
:returns: OrderedDict (attribute name, attribute value).
|
||||
"""
|
||||
data = OrderedDict()
|
||||
field = record._fields[field_name]
|
||||
|
||||
if not options['inherit_branding'] and not options['translate']:
|
||||
return data
|
||||
|
||||
data['data-oe-model'] = record._name
|
||||
data['data-oe-id'] = record.id
|
||||
data['data-oe-field'] = field.name
|
||||
data['data-oe-type'] = options.get('type')
|
||||
data['data-oe-expression'] = options.get('expression')
|
||||
if field.readonly:
|
||||
data['data-oe-readonly'] = 1
|
||||
return data
|
||||
|
||||
@api.model
|
||||
def value_to_html(self, value, field, options=None):
|
||||
""" value_to_html(value, field, options=None)
|
||||
|
||||
Converts a single value to its HTML version/output
|
||||
:rtype: unicode
|
||||
"""
|
||||
return html_escape(unicodifier(value) or u'', options)
|
||||
|
||||
@api.model
|
||||
def record_to_html(self, record, field_name, options, qwebcontext=None):
|
||||
""" record_to_html(record, field_name, options, qwebcontext)
|
||||
|
||||
Converts the specified field of the browse_record ``record`` to HTML
|
||||
:rtype: unicode
|
||||
"""
|
||||
field = record._fields[field_name]
|
||||
|
||||
try:
|
||||
value = record[field_name]
|
||||
return False if value is False else record.env[self._name].value_to_html(value, field, options=options)
|
||||
except Exception:
|
||||
_logger.warning("Could not get field %s for model %s",
|
||||
field_name, record._name, exc_info=True)
|
||||
return None
|
||||
|
||||
@api.model
|
||||
def user_lang(self):
|
||||
""" user_lang()
|
||||
|
||||
Fetches the res.lang record corresponding to the language code stored
|
||||
in the user's context. Fallbacks to en_US if no lang is present in the
|
||||
context *or the language code is not valid*.
|
||||
|
||||
:returns: res.lang browse_record
|
||||
"""
|
||||
lang_code = self._context.get('lang') or 'en_US'
|
||||
return self.env['res.lang']._lang_get(lang_code)
|
||||
|
||||
|
||||
class IntegerConverter(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.integer'
|
||||
_inherit = 'ir.qweb.field'
|
||||
|
||||
@api.model
|
||||
def value_to_html(self, value, field, options=None):
|
||||
return unicodifier(self.user_lang().format('%d', value, grouping=True))
|
||||
|
||||
|
||||
class FloatConverter(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.float'
|
||||
_inherit = 'ir.qweb.field'
|
||||
|
||||
@api.model
|
||||
def precision(self, field, options=None):
|
||||
_, precision = field.digits or (None, None)
|
||||
return precision
|
||||
|
||||
@api.model
|
||||
def value_to_html(self, value, field, options=None):
|
||||
precision = self.precision(field, options=options)
|
||||
fmt = '%f' if precision is None else '%.{precision}f'
|
||||
lang = self.user_lang()
|
||||
formatted = lang.format(fmt.format(precision=precision), value, grouping=True)
|
||||
|
||||
# %f does not strip trailing zeroes. %g does but its precision causes
|
||||
# it to switch to scientific notation starting at a million *and* to
|
||||
# strip decimals. So use %f and if no precision was specified manually
|
||||
# strip trailing 0.
|
||||
if precision is None:
|
||||
formatted = re.sub(r'(?:(0|\d+?)0+)$', r'\1', formatted)
|
||||
return unicodifier(formatted)
|
||||
|
||||
|
||||
class DateConverter(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.date'
|
||||
_inherit = 'ir.qweb.field'
|
||||
|
||||
@api.model
|
||||
def value_to_html(self, value, field, options=None):
|
||||
if not value or len(value) < 10:
|
||||
return ''
|
||||
lang = self.user_lang()
|
||||
locale = babel.Locale.parse(lang.code)
|
||||
|
||||
if isinstance(value, basestring):
|
||||
value = fields.Datetime.from_string(value[:10])
|
||||
|
||||
if options and 'format' in options:
|
||||
pattern = options['format']
|
||||
else:
|
||||
strftime_pattern = lang.date_format
|
||||
pattern = posix_to_ldml(strftime_pattern, locale=locale)
|
||||
|
||||
return babel.dates.format_date(value, format=pattern, locale=locale)
|
||||
|
||||
|
||||
class DateTimeConverter(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.datetime'
|
||||
_inherit = 'ir.qweb.field'
|
||||
|
||||
@api.model
|
||||
def value_to_html(self, value, field, options=None):
|
||||
if not value:
|
||||
return ''
|
||||
lang = self.user_lang()
|
||||
locale = babel.Locale.parse(lang.code)
|
||||
|
||||
if isinstance(value, basestring):
|
||||
value = fields.Datetime.from_string(value)
|
||||
|
||||
value = fields.Datetime.context_timestamp(self, value)
|
||||
|
||||
if options and 'format' in options:
|
||||
pattern = options['format']
|
||||
else:
|
||||
strftime_pattern = (u"%s %s" % (lang.date_format, lang.time_format))
|
||||
pattern = posix_to_ldml(strftime_pattern, locale=locale)
|
||||
|
||||
if options and options.get('hide_seconds'):
|
||||
pattern = pattern.replace(":ss", "").replace(":s", "")
|
||||
|
||||
return unicodifier(babel.dates.format_datetime(value, format=pattern, locale=locale))
|
||||
|
||||
|
||||
class TextConverter(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.text'
|
||||
_inherit = 'ir.qweb.field'
|
||||
|
||||
@api.model
|
||||
def value_to_html(self, value, field, options=None):
|
||||
"""
|
||||
Escapes the value and converts newlines to br. This is bullshit.
|
||||
"""
|
||||
return nl2br(html_escape(value, options)) if value else ''
|
||||
|
||||
|
||||
class SelectionConverter(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.selection'
|
||||
_inherit = 'ir.qweb.field'
|
||||
|
||||
@api.model
|
||||
def value_to_html(self, value, field, options=None):
|
||||
if not value:
|
||||
return ''
|
||||
selection = dict(field.get_description(self.env)['selection'])
|
||||
return html_escape(unicodifier(selection[value]) or u'', options)
|
||||
|
||||
|
||||
class ManyToOneConverter(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.many2one'
|
||||
_inherit = 'ir.qweb.field'
|
||||
|
||||
@api.model
|
||||
def record_to_html(self, record, field_name, options, qwebcontext=None):
|
||||
value = record[field_name]
|
||||
if not value:
|
||||
return False
|
||||
value = value.sudo().display_name
|
||||
if not value:
|
||||
return False
|
||||
return nl2br(html_escape(value, options)) if value else ''
|
||||
|
||||
|
||||
class HTMLConverter(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.html'
|
||||
_inherit = 'ir.qweb.field'
|
||||
|
||||
@api.model
|
||||
def value_to_html(self, value, field, options=None):
|
||||
return unicodifier(value) or u''
|
||||
|
||||
|
||||
class ImageConverter(models.AbstractModel):
|
||||
""" ``image`` widget rendering, inserts a data:uri-using image tag in the
|
||||
document. May be overridden by e.g. the website module to generate links
|
||||
instead.
|
||||
|
||||
.. todo:: what happens if different output need different converters? e.g.
|
||||
reports may need embedded images or FS links whereas website
|
||||
needs website-aware
|
||||
"""
|
||||
_name = 'ir.qweb.field.image'
|
||||
_inherit = 'ir.qweb.field'
|
||||
|
||||
@api.model
|
||||
def value_to_html(self, value, field, options=None):
|
||||
try:
|
||||
image = Image.open(StringIO(value.decode('base64')))
|
||||
image.verify()
|
||||
except IOError:
|
||||
raise ValueError("Non-image binary fields can not be converted to HTML")
|
||||
except: # image.verify() throws "suitable exceptions", I have no idea what they are
|
||||
raise ValueError("Invalid image content")
|
||||
|
||||
return unicodifier('<img src="data:%s;base64,%s">' % (Image.MIME[image.format], value))
|
||||
|
||||
|
||||
class MonetaryConverter(models.AbstractModel):
|
||||
""" ``monetary`` converter, has a mandatory option
|
||||
``display_currency`` only if field is not of type Monetary.
|
||||
Otherwise, if we are in presence of a monetary field, the field definition must
|
||||
have a currency_field attribute set.
|
||||
|
||||
The currency is used for formatting *and rounding* of the float value. It
|
||||
is assumed that the linked res_currency has a non-empty rounding value and
|
||||
res.currency's ``round`` method is used to perform rounding.
|
||||
|
||||
.. note:: the monetary converter internally adds the qweb context to its
|
||||
options mapping, so that the context is available to callees.
|
||||
It's set under the ``_qwebcontext`` key.
|
||||
"""
|
||||
_name = 'ir.qweb.field.monetary'
|
||||
_inherit = 'ir.qweb.field'
|
||||
|
||||
@api.model
|
||||
def record_to_html(self, record, field_name, options, qwebcontext=None):
|
||||
field = record._fields[field_name]
|
||||
if qwebcontext is None:
|
||||
qwebcontext = QWebContext(self.env, qwebcontext or {})
|
||||
|
||||
display_currency = False
|
||||
#currency should be specified by monetary field
|
||||
if field.type == 'monetary' and field.currency_field:
|
||||
display_currency = record[field.currency_field]
|
||||
#otherwise fall back to old method
|
||||
if not display_currency:
|
||||
display_currency = self.display_currency(options['display_currency'], qwebcontext)
|
||||
|
||||
# lang.format mandates a sprintf-style format. These formats are non-
|
||||
# minimal (they have a default fixed precision instead), and
|
||||
# lang.format will not set one by default. currency.round will not
|
||||
# provide one either. So we need to generate a precision value
|
||||
# (integer > 0) from the currency's rounding (a float generally < 1.0).
|
||||
fmt = "%.{0}f".format(display_currency.decimal_places)
|
||||
|
||||
from_amount = record[field_name]
|
||||
|
||||
if options.get('from_currency'):
|
||||
from_currency = self.display_currency(options['from_currency'], qwebcontext)
|
||||
from_amount = from_currency.compute(from_amount, display_currency)
|
||||
|
||||
lang = self.user_lang()
|
||||
formatted_amount = lang.format(fmt, display_currency.round(from_amount),
|
||||
grouping=True, monetary=True).replace(r' ', u'\N{NO-BREAK SPACE}')
|
||||
|
||||
pre = post = u''
|
||||
if display_currency.position == 'before':
|
||||
pre = u'{symbol}\N{NO-BREAK SPACE}'
|
||||
else:
|
||||
post = u'\N{NO-BREAK SPACE}{symbol}'
|
||||
|
||||
return u'{pre}<span class="oe_currency_value">{0}</span>{post}'.format(
|
||||
formatted_amount, pre=pre, post=post,
|
||||
).format(
|
||||
symbol=display_currency.symbol,
|
||||
)
|
||||
|
||||
@api.model
|
||||
def display_currency(self, currency, qwebcontext):
|
||||
return eval_expr(currency, qwebcontext)
|
||||
|
||||
|
||||
TIMEDELTA_UNITS = (
|
||||
('year', 3600 * 24 * 365),
|
||||
('month', 3600 * 24 * 30),
|
||||
('week', 3600 * 24 * 7),
|
||||
('day', 3600 * 24),
|
||||
('hour', 3600),
|
||||
('minute', 60),
|
||||
('second', 1)
|
||||
)
|
||||
|
||||
|
||||
class DurationConverter(models.AbstractModel):
|
||||
""" ``duration`` converter, to display integral or fractional values as
|
||||
human-readable time spans (e.g. 1.5 as "1 hour 30 minutes").
|
||||
|
||||
Can be used on any numerical field.
|
||||
|
||||
Has a mandatory option ``unit`` which can be one of ``second``, ``minute``,
|
||||
``hour``, ``day``, ``week`` or ``year``, used to interpret the numerical
|
||||
field value before converting it.
|
||||
|
||||
Sub-second values will be ignored.
|
||||
"""
|
||||
_name = 'ir.qweb.field.duration'
|
||||
_inherit = 'ir.qweb.field'
|
||||
|
||||
@api.model
|
||||
def value_to_html(self, value, field, options=None):
|
||||
units = dict(TIMEDELTA_UNITS)
|
||||
if value < 0:
|
||||
raise ValueError(_("Durations can't be negative"))
|
||||
if not options or options.get('unit') not in units:
|
||||
raise ValueError(_("A unit must be provided to duration widgets"))
|
||||
|
||||
locale = babel.Locale.parse(self.user_lang().code)
|
||||
factor = units[options['unit']]
|
||||
|
||||
sections = []
|
||||
r = value * factor
|
||||
for unit, secs_per_unit in TIMEDELTA_UNITS:
|
||||
v, r = divmod(r, secs_per_unit)
|
||||
if not v:
|
||||
continue
|
||||
section = babel.dates.format_timedelta(
|
||||
v*secs_per_unit, threshold=1, locale=locale)
|
||||
if section:
|
||||
sections.append(section)
|
||||
return u' '.join(sections)
|
||||
|
||||
|
||||
class RelativeDatetimeConverter(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.relative'
|
||||
_inherit = 'ir.qweb.field'
|
||||
|
||||
@api.model
|
||||
def value_to_html(self, value, field, options=None):
|
||||
locale = babel.Locale.parse(self.user_lang().code)
|
||||
|
||||
if isinstance(value, basestring):
|
||||
value = fields.Datetime.from_string(value)
|
||||
|
||||
# value should be a naive datetime in UTC. So is fields.Datetime.now()
|
||||
reference = fields.Datetime.from_string(field.now())
|
||||
|
||||
return unicodifier(babel.dates.format_timedelta(value - reference, add_direction=True, locale=locale))
|
||||
|
||||
|
||||
class Contact(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.contact'
|
||||
_inherit = 'ir.qweb.field.many2one'
|
||||
|
||||
@api.model
|
||||
def record_to_html(self, record, field_name, options, qwebcontext=None):
|
||||
opf = options and options.get('fields') or ["name", "address", "phone", "mobile", "fax", "email"]
|
||||
|
||||
value_rec = record[field_name]
|
||||
if not value_rec:
|
||||
return None
|
||||
value_rec = value_rec.sudo().with_context(show_address=True)
|
||||
value = value_rec.name_get()[0][1]
|
||||
|
||||
val = {
|
||||
'name': value.split("\n")[0],
|
||||
'address': escape("\n".join(value.split("\n")[1:])).strip(),
|
||||
'phone': value_rec.phone,
|
||||
'mobile': value_rec.mobile,
|
||||
'fax': value_rec.fax,
|
||||
'city': value_rec.city,
|
||||
'country_id': value_rec.country_id.display_name,
|
||||
'website': value_rec.website,
|
||||
'email': value_rec.email,
|
||||
'fields': opf,
|
||||
'object': value_rec,
|
||||
'options': options
|
||||
}
|
||||
|
||||
return unicodifier(self.env.ref('base.contact').render(val, engine='ir.qweb'))
|
||||
|
||||
|
||||
class QwebView(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.qweb'
|
||||
_inherit = 'ir.qweb.field.many2one'
|
||||
|
||||
@api.model
|
||||
def record_to_html(self, record, field_name, options, qwebcontext=None):
|
||||
if not getattr(record, field_name):
|
||||
return None
|
||||
|
||||
view = getattr(record, field_name)
|
||||
|
||||
if view._name != "ir.ui.view":
|
||||
_logger.warning("%s.%s must be a 'ir.ui.view' model." % (record, field_name))
|
||||
return None
|
||||
|
||||
view = view.with_context(object=record)
|
||||
|
||||
return unicodifier(view.render(view._context, engine='ir.qweb'))
|
||||
@@ -0,0 +1,266 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import ast
|
||||
from urlparse import urlparse
|
||||
from lxml import html
|
||||
import math
|
||||
|
||||
from .qweb import QWeb, Contextifier
|
||||
from .assetsbundle import AssetsBundle
|
||||
from lxml import etree
|
||||
|
||||
from openerp import api, models, tools
|
||||
from openerp.tools import safe_eval
|
||||
from openerp.addons.web.http import request
|
||||
import json
|
||||
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ir_QWeb(models.AbstractModel, QWeb):
|
||||
""" Base QWeb rendering engine
|
||||
* to customize ``t-field`` rendering, subclass ``ir.qweb.field`` and
|
||||
create new models called :samp:`ir.qweb.field.{widget}`
|
||||
Beware that if you need extensions or alterations which could be
|
||||
incompatible with other subsystems, you should create a local object
|
||||
inheriting from ``ir.qweb`` and customize that.
|
||||
"""
|
||||
|
||||
_name = 'ir.qweb'
|
||||
|
||||
@api.model
|
||||
def render(self, id_or_xml_id, values=None, **options):
|
||||
""" render(id_or_xml_id, values=None, **options)
|
||||
Renders the template specified by the provided template name
|
||||
:param values: context for rendering the template
|
||||
:type values: dict or :class:`values` instance
|
||||
"""
|
||||
for method in dir(self):
|
||||
if method.startswith('render_'):
|
||||
_logger.warning("Unused method '%s' is found in ir.qweb." % method)
|
||||
|
||||
body = []
|
||||
|
||||
import time
|
||||
_ = []
|
||||
ap = _.append
|
||||
t = time.time()
|
||||
def append(data):
|
||||
ap([time.time()-t, data])
|
||||
body.append(data)
|
||||
|
||||
context = dict(self.env.context, dev_mode='qweb' in tools.config['dev_mode'])
|
||||
context.update(options)
|
||||
|
||||
self.compile(id_or_xml_id, context)(self, append, values or {})
|
||||
|
||||
# t = _[0][0]
|
||||
# for v in _:
|
||||
# print round((v[0]-t)*1000000)/1000, v[1]
|
||||
|
||||
return u''.join(body)
|
||||
|
||||
# assume cache will be invalidated by third party on write to ir.ui.view
|
||||
def _get_template_cache_keys(self):
|
||||
""" Return the list of context keys to use for caching ``_get_template``. """
|
||||
return ['lang', 'inherit_branding', 'editable', 'translatable', 'edit_translations']
|
||||
|
||||
# apply ormcache_context decorator unless in dev mode...
|
||||
@tools.conditional(
|
||||
'xml' not in tools.config['dev_mode'],
|
||||
tools.ormcache('id_or_xml_id', 'tuple(map(options.get, self._get_template_cache_keys()))'),
|
||||
)
|
||||
def compile(self, id_or_xml_id, options):
|
||||
return super(ir_QWeb, self).compile(id_or_xml_id, options=options)
|
||||
|
||||
def load(self, name, options):
|
||||
lang = options.get('lang', 'en_US')
|
||||
env = self.env
|
||||
if lang != env.context.get('lang'):
|
||||
env = env(context=dict(env.context, lang=lang))
|
||||
template = env['ir.ui.view'].read_template(name)
|
||||
|
||||
res_id = isinstance(name, (int, long)) and name or None
|
||||
if res_id:
|
||||
for node in etree.fromstring(template):
|
||||
if node.get('t-name'):
|
||||
return node
|
||||
elif res_id and node.tag == "t":
|
||||
node.set('t-name', str(res_id))
|
||||
return node
|
||||
|
||||
return template
|
||||
|
||||
# order
|
||||
|
||||
def _directives_eval_order(self):
|
||||
directives = super(ir_QWeb, self)._directives_eval_order()
|
||||
directives.insert(directives.index('call'), 'lang')
|
||||
directives.insert(directives.index('field'), 'call-assets')
|
||||
return directives
|
||||
|
||||
# compile directives
|
||||
|
||||
def _compile_directive_lang(self, el, options):
|
||||
el.set('t-call-options', '{"lang": %s}' % el.attrib.pop('t-lang', 'en_US'))
|
||||
return self._compile_node(el, options)
|
||||
|
||||
def _compile_directive_call_assets(self, el, options):
|
||||
""" This special 't-call' tag can be used in order to aggregate/minify javascript and css assets"""
|
||||
if len(el):
|
||||
raise "t-call-assets cannot contain children nodes"
|
||||
|
||||
# self._get_asset(xmlid, options).to_html(css, js, debug, async, values)
|
||||
return [
|
||||
self._append(ast.Call(
|
||||
func=ast.Attribute(
|
||||
value=ast.Call(
|
||||
func=ast.Attribute(
|
||||
value=ast.Name(id='self', ctx=ast.Load()),
|
||||
attr='_get_asset',
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[
|
||||
ast.Str(el.get('t-call-assets')),
|
||||
ast.Name(id='options', ctx=ast.Load()),
|
||||
],
|
||||
keywords=[], starargs=None, kwargs=None
|
||||
),
|
||||
attr='to_html',
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[],
|
||||
keywords=[
|
||||
ast.keyword('css', self._get_attr_bool(el.get('t-css', True))),
|
||||
ast.keyword('js', self._get_attr_bool(el.get('t-js', True))),
|
||||
ast.keyword('debug', ast.Call(
|
||||
func=ast.Attribute(
|
||||
value=ast.Name(id='values', ctx=ast.Load()),
|
||||
attr='get',
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[ast.Str('debug')],
|
||||
keywords=[], starargs=None, kwargs=None
|
||||
)),
|
||||
ast.keyword('async', self._get_attr_bool(el.get('async', False))),
|
||||
ast.keyword('values', ast.Name(id='values', ctx=ast.Load())),
|
||||
],
|
||||
starargs=None, kwargs=None
|
||||
))
|
||||
]
|
||||
|
||||
# method called by computing code
|
||||
|
||||
def _get_asset(self, xmlid, options):
|
||||
files, remains = self._get_asset_content(xmlid, options)
|
||||
return AssetsBundle(xmlid, files, remains, env=self.env)
|
||||
|
||||
@tools.ormcache('xmlid', 'options["lang"]')
|
||||
def _get_asset_content(self, xmlid, options):
|
||||
options = dict(options,
|
||||
inherit_branding=False, inherit_branding_auto=False,
|
||||
edit_translations=False, translatable=False,
|
||||
rendering_bundle=True)
|
||||
|
||||
env = self.env(context=options)
|
||||
|
||||
# TODO: This helper can be used by any template that wants to embedd the backend.
|
||||
# It is currently necessary because the ir.ui.view bundle inheritance does not
|
||||
# match the module dependency graph.
|
||||
def get_modules_order():
|
||||
if request:
|
||||
from odoo.addons.web.controllers.main import module_boot
|
||||
return json.dumps(module_boot())
|
||||
return '[]'
|
||||
template = env['ir.qweb'].render(xmlid, {"get_modules_order": get_modules_order})
|
||||
|
||||
files = []
|
||||
remains = []
|
||||
for el in html.fragments_fromstring(template):
|
||||
if isinstance(el, basestring):
|
||||
remains.append(el)
|
||||
elif isinstance(el, html.HtmlElement):
|
||||
href = el.get('href', '')
|
||||
atype = el.get('type')
|
||||
media = el.get('media')
|
||||
|
||||
can_aggregate = not urlparse(href).netloc and not href.startswith('/web/content')
|
||||
if el.tag == 'style' or (el.tag == 'link' and el.get('rel') == 'stylesheet' and can_aggregate):
|
||||
if href.endswith('.sass'):
|
||||
atype = 'text/sass'
|
||||
elif href.endswith('.less'):
|
||||
atype = 'text/less'
|
||||
if atype not in ('text/less', 'text/sass'):
|
||||
atype = 'text/css'
|
||||
files.append({'atype': atype, 'url': href, 'content': el.text, 'media': media})
|
||||
elif el.tag == 'script':
|
||||
atype = 'text/javascript'
|
||||
files.append({'atype': atype, 'url': el.get('src', ''), 'content': el.text, 'media': media})
|
||||
else:
|
||||
remains.append(html.tostring(el))
|
||||
else:
|
||||
try:
|
||||
remains.append(html.tostring(el))
|
||||
except Exception:
|
||||
# notYETimplementederror
|
||||
raise NotImplementedError
|
||||
|
||||
return (files, remains)
|
||||
|
||||
def _get_field(self, record, field_name, expression, default_content, tagName, field_options, options, values):
|
||||
field = record._fields[field_name]
|
||||
|
||||
field_options['tagName'] = tagName
|
||||
field_options['expression'] = expression
|
||||
field_options['default_content'] = default_content
|
||||
field_options['type'] = field_options.get('widget', field.type)
|
||||
inherit_branding = options.get('inherit_branding', options.get('inherit_branding_auto') and record.check_access_rights('write', False))
|
||||
field_options['inherit_branding'] = inherit_branding
|
||||
translate = options.get('edit_translations') and options.get('translatable') and getattr(field, 'translate', False)
|
||||
field_options['translate'] = translate
|
||||
|
||||
# field converter
|
||||
model = 'ir.qweb.field.' + field_options['type']
|
||||
converter = self.env[model] if model in self.env else self.env['ir.qweb.field']
|
||||
|
||||
# get content
|
||||
content = converter.record_to_html(record, field_name, field_options, values)
|
||||
attributes = converter.attributes(record, field_name, field_options, values)
|
||||
|
||||
return (attributes, content or default_content or (u"" if inherit_branding or translate else None))
|
||||
|
||||
# formating methods
|
||||
|
||||
def _format_func_monetary(self, value, display_currency=None, from_currency=None):
|
||||
env = display_currency.env
|
||||
precision = int(round(math.log10(display_currency.rounding)))
|
||||
fmt = "%.{0}f".format(-precision if precision < 0 else 0)
|
||||
lang = env['res.lang']._lang_get(env.context.get('lang', 'en_US'))
|
||||
formatted_amount = lang.format(fmt, value, grouping=True, monetary=True).replace(r' ', u'\N{NO-BREAK SPACE}')
|
||||
pre = post = u''
|
||||
if display_currency.position == 'before':
|
||||
pre = u'{symbol}\N{NO-BREAK SPACE}'
|
||||
else:
|
||||
post = u'\N{NO-BREAK SPACE}{symbol}'
|
||||
return u'{pre}{0}{post}'.format(
|
||||
formatted_amount, pre=pre, post=post
|
||||
).format(symbol=display_currency.symbol,)
|
||||
|
||||
# compile expression add safe_eval
|
||||
|
||||
def _compile_expr(self, expr):
|
||||
""" Compiles a purported Python expression to ast, verifies that it's safe
|
||||
(according to safe_eval's semantics) and alter its variable references to
|
||||
access values data instead
|
||||
"""
|
||||
# string must be stripped otherwise whitespace before the start for
|
||||
# formatting purpose are going to break parse/compile
|
||||
st = ast.parse(expr.strip(), mode='eval')
|
||||
safe_eval.assert_valid_codeobj(
|
||||
safe_eval._SAFE_OPCODES,
|
||||
compile(st, '<>', 'eval'), # could be expr, but eval *should* be fine
|
||||
expr
|
||||
)
|
||||
|
||||
# ast.Expression().body -> expr
|
||||
return Contextifier().visit(st).body
|
||||
@@ -0,0 +1,972 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import ast
|
||||
from collections import OrderedDict
|
||||
import json
|
||||
from urlparse import urlparse
|
||||
from lxml import etree, html
|
||||
import re
|
||||
import itertools
|
||||
import textwrap
|
||||
import traceback
|
||||
|
||||
import openerp.tools
|
||||
from odoo.exceptions import QWebException
|
||||
|
||||
from openerp.tools import html_escape
|
||||
from . import utils
|
||||
from .utils import QWebContext, unicodifier
|
||||
from .assetsbundle import AssetsBundle
|
||||
|
||||
from odoo import models
|
||||
|
||||
try:
|
||||
import astor
|
||||
except ImportError:
|
||||
astor = None
|
||||
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class QWeb(models.AbstractModel):
|
||||
""" Base QWeb rendering engine
|
||||
* to customize ``t-field`` rendering, subclass ``ir.qweb.field`` and
|
||||
create new models called :samp:`ir.qweb.field.{widget}`
|
||||
* alternatively, override :meth:`~.get_field_for` and return an
|
||||
arbitrary model to use as field converter
|
||||
Beware that if you need extensions or alterations which could be
|
||||
incompatible with other subsystems, you should create a local object
|
||||
inheriting from ``ir.qweb`` and customize that.
|
||||
"""
|
||||
|
||||
_name = 'ir.qweb'
|
||||
|
||||
def get_template(self, name, loader, origin_template=None):
|
||||
try:
|
||||
document = loader(name)
|
||||
except ValueError:
|
||||
raise QWebException("%s\nLoader could not find template %r" % (traceback.format_exc(), name), template=origin_template)
|
||||
else:
|
||||
if document is not None:
|
||||
if hasattr(document, 'documentElement'):
|
||||
dom = document
|
||||
elif document.startswith("<?xml"):
|
||||
dom = etree.fromstring(document)
|
||||
else:
|
||||
dom = etree.parse(document).getroot()
|
||||
|
||||
res_id = isinstance(name, (int, long)) and name or None
|
||||
for node in dom:
|
||||
if node.get('t-name') or (res_id and node.tag == "t"):
|
||||
return node
|
||||
|
||||
raise QWebException("%s\nTemplate %r not found" % (traceback.format_exc(), name), template=origin_template)
|
||||
|
||||
def render(self, cr, uid, id_or_xml_id, qwebcontext=None, loader=None, context=None):
|
||||
""" render(cr, uid, id_or_xml_id, qwebcontext=None, loader=None, context=None)
|
||||
Renders the template specified by the provided template name
|
||||
:param qwebcontext: context for rendering the template
|
||||
:type qwebcontext: dict or :class:`QWebContext` instance
|
||||
:param loader: if ``qwebcontext`` is a dict, loader set into the
|
||||
context instantiated for rendering
|
||||
"""
|
||||
# noinspection PyMethodFirstArgAssignment
|
||||
self = self.browse(cr, uid, [], context=context)
|
||||
if qwebcontext is None:
|
||||
qwebcontext = {}
|
||||
|
||||
if not isinstance(qwebcontext, QWebContext):
|
||||
qwebcontext = QWebContext(self.env, qwebcontext, loader=loader)
|
||||
|
||||
qwebcontext['__template__'] = id_or_xml_id
|
||||
stack = qwebcontext.get('__stack__', [])
|
||||
if stack:
|
||||
qwebcontext['__caller__'] = stack[-1]
|
||||
stack.append(id_or_xml_id)
|
||||
qwebcontext['__stack__'] = stack
|
||||
qwebcontext['xmlid'] = str(stack[0]) # Temporary fix
|
||||
|
||||
template_function = self._get_template(id_or_xml_id, qwebcontext)
|
||||
|
||||
for method in dir(self):
|
||||
if method.startswith('render_'):
|
||||
_logger.warning("Method '%s' found in ir.qweb, please remove it." % method)
|
||||
|
||||
return template_function(qwebcontext).encode('utf-8')
|
||||
|
||||
def get_field_for(self, field_type, cr=None, uid=None, context=None):
|
||||
""" returns a :class:`~openerp.models.Model` used to render a
|
||||
``t-field``.
|
||||
By default, tries to get the model named
|
||||
:samp:`ir.qweb.field.{field_type}`, falling back on ``ir.qweb.field``.
|
||||
:param str field_type: type or widget of field to render
|
||||
:param cursor cr
|
||||
:param int uid
|
||||
:param dict context
|
||||
"""
|
||||
model = 'ir.qweb.field.' + field_type
|
||||
env = self.env(cr=cr, user=uid, context=context)
|
||||
return env[model] if model in env else env['ir.qweb.field']
|
||||
|
||||
def format(value, formating, *args, **kwargs):
|
||||
format = getattr(self, '_format_func_%s' % formating, None)
|
||||
if not format:
|
||||
raise "Unknown formating '%s'" % (formating,)
|
||||
return format(value, *args, **kwargs)
|
||||
|
||||
def _format_func_monetary(self, value, display_currency=None, from_currency=None):
|
||||
precision = int(round(math.log10(display_currency.rounding)))
|
||||
fmt = "%.{0}f".format(-precision if precision < 0 else 0)
|
||||
lang_code = self.env.context.get('lang') or 'en_US'
|
||||
lang = self.env['res.lang']._lang_get(lang_code)
|
||||
formatted_amount = lang.format(fmt, value, grouping=True, monetary=True)
|
||||
pre = post = u''
|
||||
if display_currency.position == 'before':
|
||||
pre = u'{symbol}\N{NO-BREAK SPACE}'
|
||||
else:
|
||||
post = u'\N{NO-BREAK SPACE}{symbol}'
|
||||
return u'{pre}{0}{post}'.format(
|
||||
formatted_amount, pre=pre, post=post
|
||||
).format(symbol=display_currency.symbol,)
|
||||
|
||||
|
||||
_void_elements = frozenset([
|
||||
'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen',
|
||||
'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr'])
|
||||
|
||||
_name_gen = itertools.count()
|
||||
|
||||
def compile(self, element):
|
||||
xmlid = element.get('t-name', 'unknown')
|
||||
mod = utils.base_module()
|
||||
|
||||
string = etree.tostring(element)
|
||||
|
||||
try:
|
||||
ast_calls = self._compile_document(element)
|
||||
except Exception, e:
|
||||
raise QWebException("%s%s\nError when compiling AST" % (e, traceback.format_exc()), template=xmlid)
|
||||
|
||||
mod.body.extend(ast_calls)
|
||||
ast.fix_missing_locations(mod)
|
||||
|
||||
def formatException(e, message):
|
||||
if isinstance(e, QWebException):
|
||||
return e
|
||||
stack = traceback.format_exc()
|
||||
|
||||
if astor:
|
||||
code = astor.to_source(mod)
|
||||
else:
|
||||
code = "Please install astor to display the compiled code"
|
||||
stack += "\nInstall `astor` for compiled source information."
|
||||
|
||||
return QWebException(
|
||||
"%s%s\n%s\nTemplate: %s\nCompiled code:\n%s\nTemplate:\n%s" % (
|
||||
e, stack, message, xmlid, code, string), template=xmlid)
|
||||
|
||||
try:
|
||||
# noinspection PyBroadException
|
||||
ns = {}
|
||||
eval(compile(mod, '<template>', 'exec'), ns)
|
||||
except Exception, e:
|
||||
raise formatException(e, "Error when compiling AST")
|
||||
|
||||
def _compiled_fn(qw):
|
||||
try:
|
||||
return ns[ast_calls[-1].name](self, qw)
|
||||
except Exception, e:
|
||||
raise formatException(e, "Error to render compiling AST")
|
||||
|
||||
return _compiled_fn
|
||||
|
||||
def _append(self, item):
|
||||
assert isinstance(item, ast.expr) or isinstance(item, ast.Expr)
|
||||
return ast.Expr(ast.Call(
|
||||
func=ast.Attribute(
|
||||
value=ast.Name(id='output', ctx=ast.Load()),
|
||||
attr='append',
|
||||
ctx=ast.Load()
|
||||
), args=[item], keywords=[],
|
||||
starargs=None, kwargs=None
|
||||
))
|
||||
|
||||
def _extend(self, items):
|
||||
return ast.Expr(ast.Call(
|
||||
func=ast.Attribute(
|
||||
value=ast.Name(id='output', ctx=ast.Load()),
|
||||
attr='extend',
|
||||
ctx=ast.Load()
|
||||
), args=[items], keywords=[],
|
||||
starargs=None, kwargs=None
|
||||
))
|
||||
|
||||
def _if_content_is_not_Falsy(self, body, orelse):
|
||||
return ast.If(
|
||||
# if content is not None and content is not False
|
||||
test=ast.BoolOp(
|
||||
op=ast.And(),
|
||||
values=[
|
||||
ast.Compare(
|
||||
left=ast.Name(id='content', ctx=ast.Load()),
|
||||
ops=[ast.IsNot()],
|
||||
comparators=[ast.Name(id='None', ctx=ast.Load())]
|
||||
),
|
||||
ast.Compare(
|
||||
left=ast.Name(id='content', ctx=ast.Load()),
|
||||
ops=[ast.IsNot()],
|
||||
comparators=[ast.Name(id='False', ctx=ast.Load())]
|
||||
)
|
||||
]
|
||||
),
|
||||
# output.append(escape($content))
|
||||
body=body,
|
||||
# output.append(body default value)
|
||||
orelse=orelse,
|
||||
)
|
||||
|
||||
def _call_body(self, ast_calls, body, args=('self', 'qwebcontext',), prefix='fn', lineno=None):
|
||||
"""
|
||||
If ``body`` is non-empty, generates (and globally store) the
|
||||
corresponding function definition and returns the relevant ast.Call
|
||||
node.
|
||||
If ``body`` is empty, doesn't do anything and returns ``None``.
|
||||
"""
|
||||
if not body:
|
||||
return None
|
||||
name = self._make_name(prefix)
|
||||
ast_calls.append(utils.base_fn_def(body, name=name, lineno=lineno))
|
||||
return ast.Call(
|
||||
func=ast.Name(id=name, ctx=ast.Load()),
|
||||
args=[ast.Name(id=arg, ctx=ast.Load()) for arg in args],
|
||||
keywords=[], starargs=None, kwargs=None
|
||||
)
|
||||
|
||||
def _make_name(self, prefix='var'):
|
||||
return "%s_%s" % (prefix, next(self._name_gen))
|
||||
|
||||
def _compile_document(self, element):
|
||||
"""
|
||||
Compiles a document rooted in ``element`` to a Python AST.
|
||||
Calls the following hooks:
|
||||
* :samp:`_compile_directive_{name}` compiles the directive of the
|
||||
specified name to AST
|
||||
In either case, the current etree._Element is received as first
|
||||
parameter and the list of ast_calls as second. A brand new list
|
||||
of AST nodes *must* be returned (can be an empty list).
|
||||
:return a list of AST nodes, the last AST node is the template call
|
||||
"""
|
||||
ast_calls = []
|
||||
self._call_body(ast_calls,
|
||||
self._compile_directive_content(element, ast_calls),
|
||||
prefix='template_%s' % element.get('t-name', 'unknown').replace('.', '_'))
|
||||
return ast_calls
|
||||
|
||||
def _compile_node(self, el, ast_calls):
|
||||
"""
|
||||
:return ast list
|
||||
"""
|
||||
ignored = self._nondirectives_ignore()
|
||||
|
||||
if el.get("groups"):
|
||||
el.set("t-groups", el.attrib.pop("groups"))
|
||||
|
||||
directives = {
|
||||
att[2:]
|
||||
for att in el.attrib
|
||||
if att.startswith('t-')
|
||||
if not att.startswith('t-att')
|
||||
if att not in ignored
|
||||
}
|
||||
|
||||
for name in self._directives_eval_order():
|
||||
# skip directives not present on the element
|
||||
if name not in directives:
|
||||
continue
|
||||
directives.remove(name)
|
||||
mname = name.replace('-', '_')
|
||||
compile_handler = getattr(self, '_compile_directive_%s' % mname, None)
|
||||
interpret_handler = 'render_tag_%s' % mname
|
||||
if hasattr(self, interpret_handler):
|
||||
_logger.warning(
|
||||
"Directive '%s' must be AOT-compiled. Dynamic interpreter %s will ignored",
|
||||
name, interpret_handler
|
||||
)
|
||||
return compile_handler(el, ast_calls)
|
||||
if directives:
|
||||
raise "Unknown directive '%s' on %s" % ("', '".join(directives), etree.tostring(el))
|
||||
|
||||
return []
|
||||
|
||||
def _directives_eval_order(self):
|
||||
""" Should list all supported directives in the order in which they
|
||||
should evaluate when set on the same element. E.g. if a node bearing
|
||||
both ``foreach`` and ``if`` should see ``foreach`` executed before
|
||||
``if`` aka
|
||||
.. code-block:: xml
|
||||
<el t-foreach="foo" t-as="bar" t-if="bar">
|
||||
should be equivalent to
|
||||
.. code-block:: xml
|
||||
<t t-foreach="foo" t-as="bar">
|
||||
<t t-if="bar">
|
||||
<el>
|
||||
then this method should return ``['foreach', 'if']``
|
||||
"""
|
||||
return [
|
||||
'debug',
|
||||
'groups', 'foreach', 'if',
|
||||
'call-assets',
|
||||
'field',
|
||||
'tag',
|
||||
'call',
|
||||
'set',
|
||||
'esc', 'raw', 'content',
|
||||
]
|
||||
|
||||
def _nondirectives_ignore(self):
|
||||
"""
|
||||
t-* attributes existing as support for actual directives, should just
|
||||
be ignored
|
||||
:returns: set
|
||||
"""
|
||||
return {
|
||||
't-name', 't-field-options', 't-as', 't-lang',
|
||||
't-value', 't-valuef', 't-ignore', 't-js', 't-css', 't-async', 't-placeholder',
|
||||
}
|
||||
|
||||
def _serialize_static_attributes(self, el):
|
||||
nodes = []
|
||||
for key, value in el.attrib.iteritems():
|
||||
if not key.startswith('t-'):
|
||||
nodes.append((key, ast.Str(value)))
|
||||
return nodes
|
||||
|
||||
def _compile_dynamic_attributes(self, el):
|
||||
nodes = []
|
||||
for name, value in el.attrib.iteritems():
|
||||
if name.startswith('t-attf-'):
|
||||
nodes.append((name[7:], utils.compile_format(value)))
|
||||
elif name.startswith('t-att-'):
|
||||
nodes.append((name[6:], utils.compile_expr(value)))
|
||||
elif name == 't-att':
|
||||
nodes.append(ast.Call(
|
||||
func=ast.Attribute(
|
||||
value=ast.Name(id='self', ctx=ast.Load()),
|
||||
attr='_compile_dynamic_att',
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[
|
||||
ast.Str(el.tag),
|
||||
utils.compile_expr(value),
|
||||
ast.Name(id='qwebcontext', ctx=ast.Load()),
|
||||
], keywords=[],
|
||||
starargs=None, kwargs=None
|
||||
))
|
||||
return nodes
|
||||
|
||||
def _compile_dynamic_att(self, tagName, atts, qwebcontext):
|
||||
if isinstance(atts, OrderedDict):
|
||||
return atts
|
||||
if isinstance(atts, (list, tuple)) and not isinstance(atts[0], (list, tuple)):
|
||||
atts = [atts]
|
||||
if isinstance(atts, (list, tuple)):
|
||||
atts = OrderedDict(atts)
|
||||
return atts
|
||||
|
||||
def _compile_all_attributes(self, el, attr_already_created=False):
|
||||
body = []
|
||||
if any(name.startswith('t-att') or not name.startswith('t-') for name, value in el.attrib.iteritems()):
|
||||
if not attr_already_created:
|
||||
attr_already_created = True
|
||||
body.append(
|
||||
ast.Assign(
|
||||
targets=[ast.Name(id='t_attrs', ctx=ast.Store())],
|
||||
value=ast.Call(
|
||||
func=ast.Name(id='OrderedDict', ctx=ast.Load()),
|
||||
args=[],
|
||||
keywords=[], starargs=None, kwargs=None
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
items = self._serialize_static_attributes(el) + self._compile_dynamic_attributes(el)
|
||||
for item in items:
|
||||
if isinstance(item, tuple):
|
||||
body.append(ast.Assign(
|
||||
targets=[ast.Subscript(
|
||||
value=ast.Name(id='t_attrs', ctx=ast.Load()),
|
||||
slice=ast.Index(ast.Str(item[0])),
|
||||
ctx=ast.Store()
|
||||
)],
|
||||
value=item[1]
|
||||
))
|
||||
elif item:
|
||||
body.append(ast.Expr(ast.Call(
|
||||
func=ast.Attribute(
|
||||
value=ast.Name(id='t_attrs', ctx=ast.Load()),
|
||||
attr='update',
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[item],
|
||||
keywords=[],
|
||||
starargs=None, kwargs=None
|
||||
)))
|
||||
|
||||
if attr_already_created:
|
||||
# for name, value in t_attrs.iteritems():
|
||||
# if value or isinstance(value, basestring)):
|
||||
# output.append(u' ')
|
||||
# output.append(name)
|
||||
# output.append(u'="')
|
||||
# output.append(escape(unicodifier(value)))
|
||||
# output.append(u'"')
|
||||
body.append(ast.For(
|
||||
target=ast.Tuple(elts=[ast.Name(id='name', ctx=ast.Store()), ast.Name(id='value', ctx=ast.Store())], ctx=ast.Store()),
|
||||
iter=ast.Call(
|
||||
func=ast.Attribute(
|
||||
value=ast.Name(id='t_attrs', ctx=ast.Load()),
|
||||
attr='iteritems',
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[], keywords=[],
|
||||
starargs=None, kwargs=None
|
||||
),
|
||||
body=[ast.If(
|
||||
test=ast.BoolOp(
|
||||
op=ast.Or(),
|
||||
values=[
|
||||
ast.Name(id='value', ctx=ast.Load()),
|
||||
ast.Call(
|
||||
func=ast.Name(id='isinstance', ctx=ast.Load()),
|
||||
args=[
|
||||
ast.Name(id='value', ctx=ast.Load()),
|
||||
ast.Name(id='basestring', ctx=ast.Load())
|
||||
],
|
||||
keywords=[],
|
||||
starargs=None, kwargs=None
|
||||
)
|
||||
]
|
||||
),
|
||||
body=[
|
||||
self._append(ast.Str(u' ')),
|
||||
self._append(ast.Name(id='name', ctx=ast.Load())),
|
||||
self._append(ast.Str(u'="')),
|
||||
self._append(ast.Call(
|
||||
func=ast.Name(id='escape', ctx=ast.Load()),
|
||||
args=[
|
||||
ast.Call(
|
||||
func=ast.Name(id='unicodifier', ctx=ast.Load()),
|
||||
args=[ast.Name(id='value', ctx=ast.Load())], keywords=[],
|
||||
starargs=None, kwargs=None
|
||||
)
|
||||
], keywords=[],
|
||||
starargs=None, kwargs=None
|
||||
)),
|
||||
self._append(ast.Str(u'"')),
|
||||
],
|
||||
orelse=[]
|
||||
)],
|
||||
orelse=[]
|
||||
))
|
||||
|
||||
return body
|
||||
|
||||
def _compile_tag(self, el, content, attr_already_created=False):
|
||||
body = [self._append(ast.Str(u'<%s' % el.tag))]
|
||||
body.extend(self._compile_all_attributes(el, attr_already_created))
|
||||
if el.tag in self._void_elements:
|
||||
body.append(self._append(ast.Str(u'/>')))
|
||||
body.extend(content)
|
||||
else:
|
||||
body.append(self._append(ast.Str(u'>')))
|
||||
body.extend(content)
|
||||
body.append(self._append(ast.Str(u'</%s>' % el.tag)))
|
||||
return body
|
||||
|
||||
def _compile_directive_debug(self, el, ast_calls):
|
||||
debugger = el.attrib.pop('t-debug')
|
||||
body = self._compile_node(el, ast_calls)
|
||||
if 'qweb' in openerp.tools.config['dev_mode']:
|
||||
body = ast.parse("__import__('%s').set_trace()" % re.sub('[^a-zA-Z]', '', debugger)).body + body # pdb, ipdb, pudb, ...
|
||||
else:
|
||||
_logger.warning("@t-debug in template is only available in --dev mode")
|
||||
return body
|
||||
|
||||
def _compile_directive_tag(self, el, ast_calls):
|
||||
el.attrib.pop('t-tag', None)
|
||||
content = self._compile_node(el, ast_calls)
|
||||
if el.tag == 't':
|
||||
return content
|
||||
return self._compile_tag(el, content, False)
|
||||
|
||||
def _compile_directive_set(self, el, ast_calls):
|
||||
if 't-value' in el.attrib:
|
||||
value = utils.compile_expr(el.attrib.pop('t-value'))
|
||||
elif 't-valuef' in el.attrib:
|
||||
value = utils.compile_format(el.attrib.pop('t-valuef'))
|
||||
else:
|
||||
render = self._call_body(ast_calls, self._compile_directive_content(el, ast_calls), prefix='set', lineno=el.sourceline)
|
||||
if render is None:
|
||||
value = ast.Str(u'')
|
||||
else:
|
||||
# concat body render to string
|
||||
value = ast.Call(
|
||||
func=ast.Attribute(value=ast.Str(u''), attr='join', ctx=ast.Load()),
|
||||
args=[render], keywords=[],
|
||||
starargs=None, kwargs=None
|
||||
)
|
||||
|
||||
return [
|
||||
ast.Assign(
|
||||
targets=[ast.Subscript(
|
||||
value=ast.Name(id='qwebcontext', ctx=ast.Load()),
|
||||
slice=ast.Index(ast.Str(el.attrib.pop('t-set'))),
|
||||
ctx=ast.Store()
|
||||
)],
|
||||
value=value
|
||||
)]
|
||||
|
||||
def _compile_directive_esc(self, el, ast_calls):
|
||||
return [
|
||||
# content = t-raw value
|
||||
ast.Assign(
|
||||
targets=[ast.Name(id='content', ctx=ast.Store())],
|
||||
value=utils.compile_expr0(el.attrib.pop('t-esc')),
|
||||
),
|
||||
# if content is not None: display
|
||||
self._if_content_is_not_Falsy(
|
||||
# output.append(escape($content))
|
||||
body=[self._append(ast.Call(
|
||||
func=ast.Name(id='escape', ctx=ast.Load()),
|
||||
args=[ast.Call(
|
||||
func=ast.Name(id='unicodifier', ctx=ast.Load()),
|
||||
args=[ast.Name(id='content', ctx=ast.Load())], keywords=[],
|
||||
starargs=None, kwargs=None
|
||||
)],
|
||||
keywords=[], starargs=None, kwargs=None
|
||||
))],
|
||||
# output.append(body default value)
|
||||
orelse=self._compile_directive_content(el, ast_calls),
|
||||
)
|
||||
]
|
||||
|
||||
def _compile_directive_raw(self, el, ast_calls):
|
||||
return [
|
||||
# content = t-raw value
|
||||
ast.Assign(
|
||||
targets=[ast.Name(id='content', ctx=ast.Store())],
|
||||
value=utils.compile_expr0(el.attrib.pop('t-raw')),
|
||||
),
|
||||
self._if_content_is_not_Falsy(
|
||||
# output.append($content)
|
||||
body=[self._append(ast.Call(
|
||||
func=ast.Name(id='unicodifier', ctx=ast.Load()),
|
||||
args=[ast.Name(id='content', ctx=ast.Load())], keywords=[],
|
||||
starargs=None, kwargs=None
|
||||
))],
|
||||
# output.append(body default value)
|
||||
orelse=self._compile_directive_content(el, ast_calls),
|
||||
)
|
||||
]
|
||||
|
||||
def _compile_directive_content(self, el, ast_calls):
|
||||
body = []
|
||||
if el.text is not None:
|
||||
body.append(self._append(ast.Str(unicodifier(el.text))))
|
||||
if el.getchildren():
|
||||
for item in el:
|
||||
# ignore comments & processing instructions
|
||||
if isinstance(item, etree._Comment):
|
||||
continue
|
||||
item.set('t-tag', item.tag)
|
||||
if not (set(['t-esc', 't-raw', 't-field']) & set(item.attrib)):
|
||||
item.set('t-content', 'True')
|
||||
body.extend(self._compile_node(item, ast_calls))
|
||||
body.extend(self._compile_tail(item))
|
||||
return body
|
||||
|
||||
def _compile_directive_call(self, el, ast_calls):
|
||||
"""
|
||||
:param etree._Element el:
|
||||
:param list ast call:
|
||||
:return: new body
|
||||
:rtype: list(ast.AST)
|
||||
"""
|
||||
tmpl = el.attrib.pop('t-call')
|
||||
qw = self._make_name('qwebcontext_copy')
|
||||
sub_content = self._call_body(ast_calls, self._compile_directive_content(el, ast_calls),
|
||||
prefix='body_call_content', args=('self', qw,), lineno=el.sourceline)
|
||||
lang = el.get('t-lang', "'en_US'")
|
||||
|
||||
content = [
|
||||
# qwebcontext_copy = qwebcontext.copy()
|
||||
ast.Assign(
|
||||
targets=[ast.Name(id=qw, ctx=ast.Store())],
|
||||
value=ast.Call(
|
||||
func=ast.Attribute(
|
||||
value=ast.Name(id='qwebcontext', ctx=ast.Load()),
|
||||
attr='copy',
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[], keywords=[],
|
||||
starargs=None, kwargs=None
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
qw_0_ast = ast.Subscript(
|
||||
value=ast.Name(id=qw, ctx=ast.Load()),
|
||||
slice=ast.Index(ast.Num(0)),
|
||||
ctx=ast.Store()
|
||||
)
|
||||
|
||||
# qwebcontext_copy[0] = body_call_content or u''
|
||||
content.append(
|
||||
ast.Assign(
|
||||
targets=[qw_0_ast],
|
||||
value=sub_content or ast.Str(u'')
|
||||
))
|
||||
|
||||
# qwebcontext_copy.env = qwebcontext.env(context=dict(qwebcontext.env.context, lang=$lang))
|
||||
if lang:
|
||||
content.append(
|
||||
ast.Assign(
|
||||
targets=[ast.Attribute(
|
||||
value=ast.Name(id=qw, ctx=ast.Load()),
|
||||
attr='env',
|
||||
ctx=ast.Store()
|
||||
)],
|
||||
value=ast.Call(
|
||||
func=ast.Attribute(
|
||||
value=ast.Name(id='qwebcontext', ctx=ast.Load()),
|
||||
attr='env',
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[],
|
||||
keywords=[ast.keyword('context', ast.Call(
|
||||
func=ast.Name(id='dict', ctx=ast.Load()),
|
||||
args=[ast.Attribute(
|
||||
value=ast.Attribute(
|
||||
value=ast.Name(id='qwebcontext', ctx=ast.Load()),
|
||||
attr='env',
|
||||
ctx=ast.Load()
|
||||
),
|
||||
attr='context',
|
||||
ctx=ast.Load()
|
||||
)],
|
||||
keywords=[ast.keyword('lang', utils.compile_strexpr(lang))],
|
||||
starargs=None, kwargs=None
|
||||
))],
|
||||
starargs=None, kwargs=None
|
||||
)
|
||||
))
|
||||
|
||||
# qwebcontext.env['ir.qweb']
|
||||
compute_self = ast.Subscript(
|
||||
value=ast.Attribute(
|
||||
value=ast.Name(id=qw, ctx=ast.Load()),
|
||||
attr='env',
|
||||
ctx=ast.Load()
|
||||
),
|
||||
slice=ast.Index(ast.Str('ir.qweb')),
|
||||
ctx=ast.Load()
|
||||
)
|
||||
else:
|
||||
compute_self = ast.Name(id='self', ctx=ast.Load())
|
||||
|
||||
# ouput.append(self._get_template($tmpl, qwebcontext)(self, qwebcontext_copy))
|
||||
content.append(
|
||||
self._append(
|
||||
ast.Call(
|
||||
func=ast.Call(
|
||||
func=ast.Attribute(
|
||||
value=compute_self,
|
||||
attr='_get_template',
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[
|
||||
ast.Str(str(tmpl)),
|
||||
ast.Name(id=qw, ctx=ast.Load()),
|
||||
],
|
||||
keywords=[], starargs=None, kwargs=None
|
||||
),
|
||||
args=[ast.Name(id=qw, ctx=ast.Load())],
|
||||
keywords=[], starargs=None, kwargs=None
|
||||
)
|
||||
))
|
||||
return content
|
||||
|
||||
def _compile_directive_if(self, el, ast_calls):
|
||||
orelse = []
|
||||
if el.tail and re.search(r'\S', el.tail):
|
||||
next = el.getnext()
|
||||
if next is not None and 't-else' in next.attrib:
|
||||
el.tail = None
|
||||
next.attrib.pop('t-else')
|
||||
orelse = self._compile_node(next, ast_calls)
|
||||
next.getparent().remove(next)
|
||||
return [
|
||||
ast.If(
|
||||
test=utils.compile_expr(el.attrib.pop('t-if')),
|
||||
body=self._compile_node(el, ast_calls),
|
||||
orelse=orelse
|
||||
)
|
||||
]
|
||||
|
||||
def _compile_directive_groups(self, el, ast_calls):
|
||||
return [
|
||||
ast.If(
|
||||
test=ast.Call(
|
||||
func=ast.Attribute(
|
||||
value=ast.Name(id='self', ctx=ast.Load()),
|
||||
attr='user_has_groups',
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[ast.Str(el.attrib.pop('t-groups'))], keywords=[],
|
||||
starargs=None, kwargs=None
|
||||
),
|
||||
body=self._compile_node(el, ast_calls),
|
||||
orelse=[]
|
||||
)
|
||||
]
|
||||
|
||||
def _compile_directive_foreach(self, el, ast_calls):
|
||||
expr = utils.compile_expr(el.attrib.pop('t-foreach'))
|
||||
varname = el.attrib.pop('t-as').replace('.', '_')
|
||||
|
||||
# foreach_iterator(qwebcontext, <expr>, varname)
|
||||
it = ast.Call(
|
||||
func=ast.Name(id='foreach_iterator', ctx=ast.Load()),
|
||||
args=[ast.Name(id='qwebcontext', ctx=ast.Load()), expr, ast.Str(varname)],
|
||||
keywords=[], starargs=None, kwargs=None
|
||||
)
|
||||
# itertools.repeat(self)
|
||||
selfs = ast.Call(
|
||||
func=ast.Attribute(
|
||||
value=ast.Name(id='itertools', ctx=ast.Load()),
|
||||
attr='repeat',
|
||||
ctx=ast.Load()
|
||||
), args=[ast.Name(id='self', ctx=ast.Load())], keywords=[],
|
||||
starargs=None, kwargs=None
|
||||
)
|
||||
|
||||
# create function $foreach
|
||||
call = self._call_body(ast_calls, self._compile_node(el, ast_calls), prefix='foreach', lineno=el.sourceline)
|
||||
|
||||
# itertools.imap($foreach(), repeat(self), it)
|
||||
it = ast.Call(
|
||||
func=ast.Attribute(
|
||||
value=ast.Name(id='itertools', ctx=ast.Load()),
|
||||
attr='imap',
|
||||
ctx=ast.Load()
|
||||
), args=[call.func, selfs, it], keywords=[],
|
||||
starargs=None, kwargs=None
|
||||
)
|
||||
# itertools.chain.from_iterable(previous)
|
||||
it = ast.Call(
|
||||
func=ast.Attribute(
|
||||
value=ast.Attribute(
|
||||
value=ast.Name(id='itertools', ctx=ast.Load()),
|
||||
attr='chain',
|
||||
ctx=ast.Load()
|
||||
),
|
||||
attr='from_iterable',
|
||||
ctx=ast.Load()
|
||||
), args=[it], keywords=[],
|
||||
starargs=None, kwargs=None
|
||||
)
|
||||
return [self._extend(it)]
|
||||
|
||||
def _compile_tail(self, el):
|
||||
return el.tail is not None and [self._append(ast.Str(unicodifier(el.tail)))] or []
|
||||
|
||||
def _compile_directive_call_assets(self, el, ast_calls):
|
||||
""" This special 't-call' tag can be used in order to aggregate/minify javascript and css assets"""
|
||||
if len(el):
|
||||
raise "t-call-assets cannot contain children nodes"
|
||||
|
||||
# self._get_asset(xmlid, qwebcontext).to_html(css, js, debug, asunc qwebcontext)
|
||||
return [
|
||||
self._append(ast.Call(
|
||||
func=ast.Attribute(
|
||||
value=ast.Call(
|
||||
func=ast.Attribute(
|
||||
value=ast.Name(id='self', ctx=ast.Load()),
|
||||
attr='_get_asset',
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[
|
||||
ast.Str(el.get('t-call-assets')),
|
||||
ast.Name(id='qwebcontext', ctx=ast.Load()),
|
||||
],
|
||||
keywords=[], starargs=None, kwargs=None
|
||||
),
|
||||
attr='to_html',
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[],
|
||||
keywords=[
|
||||
ast.keyword('css', utils.get_attr_bool(el.get('t-css', True))),
|
||||
ast.keyword('js', utils.get_attr_bool(el.get('t-js', True))),
|
||||
ast.keyword('debug', ast.Call(
|
||||
func=ast.Attribute(
|
||||
value=ast.Name(id='qwebcontext', ctx=ast.Load()),
|
||||
attr='get',
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[ast.Str('debug')],
|
||||
keywords=[], starargs=None, kwargs=None
|
||||
)),
|
||||
ast.keyword('async', utils.get_attr_bool(el.get('async', False))),
|
||||
ast.keyword('qwebcontext', ast.Name(id='qwebcontext', ctx=ast.Load())),
|
||||
],
|
||||
starargs=None, kwargs=None
|
||||
))
|
||||
]
|
||||
|
||||
def _compile_directive_field(self, el, ast_calls):
|
||||
""" eg: <span t-record="browse_record(res.partner, 1)" t-field="phone">+1 555 555 8069</span>"""
|
||||
node_name = el.tag
|
||||
assert node_name not in ("table", "tbody", "thead", "tfoot", "tr", "td",
|
||||
"li", "ul", "ol", "dl", "dt", "dd"),\
|
||||
"RTE widgets do not work correctly on %r elements" % node_name
|
||||
assert node_name != 't',\
|
||||
"t-field can not be used on a t element, provide an actual HTML node"
|
||||
assert "." in el.get('t-field'),\
|
||||
"t-field must have at least a dot like 'record.field_name'"
|
||||
|
||||
expression = el.attrib.pop('t-field')
|
||||
options = el.attrib.pop('t-field-options', None)
|
||||
record, field_name = expression.rsplit('.', 1)
|
||||
|
||||
return [
|
||||
# record
|
||||
ast.Assign(
|
||||
targets=[ast.Name(id='record', ctx=ast.Store())],
|
||||
value=utils.compile_expr(record)
|
||||
),
|
||||
# default_content = u''.join($body_content())
|
||||
ast.Assign(
|
||||
targets=[ast.Name(id='default_content', ctx=ast.Store())],
|
||||
value=self._call_body(ast_calls, self._compile_node(el, ast_calls), prefix='body_content', lineno=el.sourceline)
|
||||
),
|
||||
# t_attrs, content = self._get_field(record, field_name, expression, options, qwebcontext)
|
||||
ast.Assign(
|
||||
targets=[ast.Tuple(elts=[ast.Name(id='t_attrs', ctx=ast.Store()), ast.Name(id='content', ctx=ast.Store())], ctx=ast.Store())],
|
||||
value=ast.Call(
|
||||
func=ast.Attribute(
|
||||
value=ast.Name(id='self', ctx=ast.Load()),
|
||||
attr='_get_field',
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[
|
||||
ast.Name(id='record', ctx=ast.Load()),
|
||||
ast.Str(field_name),
|
||||
ast.Str(expression),
|
||||
ast.Name(id='default_content', ctx=ast.Load()),
|
||||
ast.Str(node_name),
|
||||
options and utils.compile_format(options) or ast.Name(id='None', ctx=ast.Load()),
|
||||
ast.Name(id='qwebcontext', ctx=ast.Load()),
|
||||
],
|
||||
keywords=[], starargs=None, kwargs=None
|
||||
),
|
||||
),
|
||||
|
||||
# if content is not None: display the tag
|
||||
self._if_content_is_not_Falsy(
|
||||
body=self._compile_tag(el, [self._append(ast.Name(id='content', ctx=ast.Load()))], True),
|
||||
orelse=[]
|
||||
)
|
||||
]
|
||||
|
||||
# assume cache will be invalidated by third party on write to ir.ui.view
|
||||
def _get_template_cache_keys(self):
|
||||
""" Return the list of context keys to use for caching ``_get_template``. """
|
||||
return ['lang', 'inherit_branding', 'editable', 'translatable', 'edit_translations']
|
||||
|
||||
# apply ormcache_context decorator unless in dev mode...
|
||||
@openerp.tools.conditional(
|
||||
'xml' not in openerp.tools.config['dev_mode'],
|
||||
openerp.tools.ormcache('xmlid', 'tuple(map(self._context.get, self._get_template_cache_keys()))'),
|
||||
)
|
||||
def _get_template(self, xmlid, qwebcontext):
|
||||
element = self.get_template(xmlid, qwebcontext.loader, origin_template=qwebcontext.get('__caller__') or qwebcontext.get('__stack__', [xmlid])[0])
|
||||
element.attrib.pop("name", False)
|
||||
return self.compile(element)
|
||||
|
||||
def _get_asset(self, xmlid, qwebcontext):
|
||||
files, remains = self._get_asset_content(xmlid, qwebcontext)
|
||||
return AssetsBundle(xmlid, files, remains, env=qwebcontext.env)
|
||||
|
||||
@openerp.tools.ormcache('xmlid', 'qwebcontext.env.lang')
|
||||
def _get_asset_content(self, xmlid, qwebcontext):
|
||||
context = dict(self.env.context if qwebcontext is None else qwebcontext.env.context,
|
||||
inherit_branding=False, inherit_branding_auto=False,
|
||||
edit_translations=False, translatable=False,
|
||||
rendering_bundle=True)
|
||||
|
||||
env = self.env(context=context)
|
||||
if qwebcontext is None:
|
||||
qwebcontext = QWebContext(env, {})
|
||||
else:
|
||||
qwebcontext = qwebcontext.copy()
|
||||
qwebcontext.env = env
|
||||
|
||||
template = env['ir.qweb']._get_template(xmlid, qwebcontext)(qwebcontext)
|
||||
|
||||
files = []
|
||||
remains = []
|
||||
for el in html.fragments_fromstring(template):
|
||||
if isinstance(el, basestring):
|
||||
remains.append(el)
|
||||
elif isinstance(el, html.HtmlElement):
|
||||
href = el.get('href', '')
|
||||
atype = el.get('type')
|
||||
media = el.get('media')
|
||||
|
||||
can_aggregate = not urlparse(href).netloc and not href.startswith('/web/content')
|
||||
if el.tag == 'style' or (el.tag == 'link' and el.get('rel') == 'stylesheet' and can_aggregate):
|
||||
if href.endswith('.sass'):
|
||||
atype = 'text/sass'
|
||||
elif href.endswith('.less'):
|
||||
atype = 'text/less'
|
||||
if atype not in ('text/less', 'text/sass'):
|
||||
atype = 'text/css'
|
||||
files.append({'atype': atype, 'url': href, 'content': el.text, 'media': media})
|
||||
elif el.tag == 'script':
|
||||
atype = 'text/javascript'
|
||||
files.append({'atype': atype, 'url': el.get('src', ''), 'content': el.text, 'media': media})
|
||||
else:
|
||||
remains.append(html.tostring(el))
|
||||
else:
|
||||
try:
|
||||
remains.append(html.tostring(el))
|
||||
except Exception:
|
||||
# notYETimplementederror
|
||||
raise NotImplementedError
|
||||
|
||||
return (files, remains)
|
||||
|
||||
def _get_field(self, record, field_name, expression, default_content, tagName, options, qwebcontext):
|
||||
options = options and json.loads(options) or {}
|
||||
field = record._fields[field_name]
|
||||
|
||||
field_type = options.get('widget', field.type)
|
||||
|
||||
inherit_branding = self._context.get('inherit_branding', self._context.get('inherit_branding_auto') and record.check_access_rights('write', False))
|
||||
translate = self._context.get('edit_translations') and self._context.get('translatable') and getattr(field, 'translate', False)
|
||||
|
||||
options['tagName'] = tagName
|
||||
options['type'] = field_type
|
||||
options['expression'] = expression
|
||||
options['default_content'] = default_content
|
||||
options['inherit_branding'] = inherit_branding
|
||||
options['translate'] = translate
|
||||
|
||||
converter = self.get_field_for(field_type, qwebcontext.cr, qwebcontext.uid, qwebcontext.context)
|
||||
|
||||
content = converter.record_to_html(record, field_name, options, qwebcontext)
|
||||
attributes = converter.attributes(record, field_name, options, qwebcontext)
|
||||
|
||||
return (attributes, content or default_content or (u"" if inherit_branding or translate else None))
|
||||
@@ -0,0 +1,381 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import ast
|
||||
import itertools
|
||||
from collections import Sized, Mapping, defaultdict
|
||||
from openerp.tools import safe_eval
|
||||
from odoo.exceptions import QWebException
|
||||
from copy import deepcopy
|
||||
from lxml import etree
|
||||
import sys
|
||||
import re
|
||||
|
||||
import __builtin__
|
||||
|
||||
|
||||
def base_module():
|
||||
""" module base supporting qweb template functions (provides basic
|
||||
imports and utilities)
|
||||
Currently provides:
|
||||
* collections
|
||||
* itertools
|
||||
* openerp.tools.html_escape as escape
|
||||
* unicodifier (empty string for a None or False, otherwise unicode string)
|
||||
"""
|
||||
|
||||
return ast.parse("""from collections import OrderedDict
|
||||
import itertools
|
||||
from openerp.addons.base.ir.ir_qweb.utils import unicodifier, foreach_iterator
|
||||
from openerp.tools import safe_eval, html_escape as escape
|
||||
""")
|
||||
|
||||
|
||||
def unicodifier(val):
|
||||
if val is None or val is False:
|
||||
return u''
|
||||
if isinstance(val, str):
|
||||
return val.decode('utf-8')
|
||||
return unicode(val)
|
||||
|
||||
def foreach_iterator(base_ctx, enum, name):
|
||||
ctx = base_ctx.copy()
|
||||
if not enum:
|
||||
return
|
||||
if isinstance(enum, int):
|
||||
enum = xrange(enum)
|
||||
size = None
|
||||
if isinstance(enum, Sized):
|
||||
ctx["%s_size" % name] = size = len(enum)
|
||||
if isinstance(enum, Mapping):
|
||||
enum = enum.iteritems()
|
||||
else:
|
||||
enum = itertools.izip(*itertools.tee(enum))
|
||||
value_key = '%s_value' % name
|
||||
index_key = '%s_index' % name
|
||||
first_key = '%s_first' % name
|
||||
last_key = '%s_last' % name
|
||||
parity_key = '%s_parity' % name
|
||||
even_key = '%s_even' % name
|
||||
odd_key = '%s_odd' % name
|
||||
for index, (item, value) in enumerate(enum):
|
||||
ctx[name] = item
|
||||
ctx[value_key] = value
|
||||
ctx[index_key] = index
|
||||
ctx[first_key] = index == 0
|
||||
if size is not None:
|
||||
ctx[last_key] = index + 1 == size
|
||||
if index % 2:
|
||||
ctx[parity_key] = 'odd'
|
||||
ctx[even_key] = False
|
||||
ctx[odd_key] = True
|
||||
else:
|
||||
ctx[parity_key] = 'even'
|
||||
ctx[even_key] = True
|
||||
ctx[odd_key] = False
|
||||
yield ctx
|
||||
# copy changed items back into source context (?)
|
||||
# FIXME: maybe QWebContext could provide a ChainMap-style clone?
|
||||
for k in base_ctx.keys():
|
||||
base_ctx[k] = ctx[k]
|
||||
|
||||
def base_fn_def(body, name='fn', lineno=None):
|
||||
""" Generates a "qweb function" definition:
|
||||
* takes a single ``qwebcontext`` parameter
|
||||
* defines a local ``output`` list
|
||||
* returns ``u''.join(output)``
|
||||
The provided body should be a list of ast nodes, they will be injected
|
||||
between the initialization of ``body`` and its concatenation
|
||||
"""
|
||||
|
||||
body = [ast.Assign([ast.Name(id='output', ctx=ast.Store())], ast.List(elts=[], ctx=ast.Load()))] + \
|
||||
body + \
|
||||
[ast.Return(ast.Call(
|
||||
func=ast.Attribute(value=ast.Str(u''), attr='join', ctx=ast.Load()),
|
||||
args=[ast.Name(id='output', ctx=ast.Load())], keywords=[],
|
||||
starargs=None, kwargs=None
|
||||
))]
|
||||
|
||||
fn = ast.FunctionDef(
|
||||
name=name,
|
||||
args=ast.arguments(args=[
|
||||
ast.Name('self', ast.Param()),
|
||||
ast.Name('qwebcontext', ast.Param())
|
||||
], defaults=[], vararg=None, kwarg=None),
|
||||
body=body,
|
||||
decorator_list=[])
|
||||
if lineno is not None:
|
||||
fn.lineno = lineno
|
||||
return fn
|
||||
|
||||
def get_attr_bool(attr, default=False):
|
||||
if attr:
|
||||
if attr is True:
|
||||
return ast.Name(id='True', ctx=ast.Load())
|
||||
attr = attr.lower()
|
||||
if attr in ('false', '0'):
|
||||
return ast.Name(id='False', ctx=ast.Load())
|
||||
elif attr in ('true', '1'):
|
||||
return ast.Name(id='True', ctx=ast.Load())
|
||||
return ast.Name(id=str(attr if attr is False else default), ctx=ast.Load())
|
||||
|
||||
|
||||
class Contextifier(ast.NodeTransformer):
|
||||
""" For user-provided template expressions, replaces any ``name`` by
|
||||
:sampe:`qwebcontext.eval_dict['{name}']` so all variable accesses are
|
||||
performed on the qwebcontext rather than in the "native" scope
|
||||
"""
|
||||
|
||||
# some people apparently put lambdas in template expressions. Turns out
|
||||
# the AST -> bytecode compiler does *not* appreciate parameters of lambdas
|
||||
# being converted from names to subscript expressions, and most likely the
|
||||
# reference to those parameters inside the lambda's body should probably
|
||||
# remain as-is. Because we're transforming an AST, the structure should
|
||||
# be lexical, so just store a set of "safe" parameter names and recurse
|
||||
# through the lambda using a new NodeTransformer
|
||||
def __init__(self, params=()):
|
||||
super(Contextifier, self).__init__()
|
||||
self._safe_names = tuple(params)
|
||||
|
||||
def visit_Name(self, node):
|
||||
if node.id in self._safe_names:
|
||||
return node
|
||||
|
||||
return ast.copy_location(
|
||||
ast.Subscript(
|
||||
value=ast.Attribute(
|
||||
value=ast.Name(id='qwebcontext', ctx=ast.Load()),
|
||||
attr='eval_dict',
|
||||
ctx=ast.Load()
|
||||
),
|
||||
slice=ast.Index(ast.Str(node.id)),
|
||||
ctx=ast.Load()),
|
||||
node
|
||||
)
|
||||
|
||||
def visit_Lambda(self, node):
|
||||
args = node.args
|
||||
# assume we don't have any tuple parameter, just names
|
||||
names = [arg.id for arg in args.args]
|
||||
if args.vararg: names.append(args.vararg)
|
||||
if args.kwarg: names.append(args.kwarg)
|
||||
# remap defaults in case there's any
|
||||
return ast.copy_location(ast.Lambda(
|
||||
args=ast.arguments(
|
||||
args=args.args,
|
||||
defaults=map(self.visit, args.defaults),
|
||||
vararg=args.vararg,
|
||||
kwarg=args.kwarg,
|
||||
),
|
||||
body=Contextifier(self._safe_names + tuple(names)).visit(node.body)
|
||||
), node)
|
||||
|
||||
# "lambda problem" also exists with comprehensions
|
||||
def _visit_comp(self, node):
|
||||
# CompExp(?, comprehension* generators)
|
||||
# comprehension = (expr target, expr iter, expr* ifs)
|
||||
|
||||
# collect names in generators.target
|
||||
names = tuple(
|
||||
node.id
|
||||
for gen in node.generators
|
||||
for node in ast.walk(gen.target)
|
||||
if isinstance(node, ast.Name)
|
||||
)
|
||||
transformer = Contextifier(self._safe_names + names)
|
||||
# copy node
|
||||
newnode = ast.copy_location(type(node)(), node)
|
||||
# then visit the comp ignoring those names, transformation is
|
||||
# probably expensive but shouldn't be many comprehensions
|
||||
for field, value in ast.iter_fields(node):
|
||||
# map transformation of comprehensions
|
||||
if isinstance(value, list):
|
||||
setattr(newnode, field, map(transformer.visit, value))
|
||||
else: # set transformation of key/value/expr fields
|
||||
setattr(newnode, field, transformer.visit(value))
|
||||
return newnode
|
||||
visit_GeneratorExp = visit_ListComp = visit_SetComp = visit_DictComp = _visit_comp
|
||||
|
||||
|
||||
def compile_strexpr(expr):
|
||||
# ensure result is unicode
|
||||
return ast.Call(
|
||||
func=ast.Name(id='unicodifier', ctx=ast.Load()),
|
||||
args=[compile_expr(expr)], keywords=[],
|
||||
starargs=None, kwargs=None
|
||||
)
|
||||
|
||||
def compile_expr0(expr):
|
||||
if expr == "0":
|
||||
return ast.Call(
|
||||
func=ast.Attribute(
|
||||
value=ast.Name(id='qwebcontext', ctx=ast.Load()),
|
||||
attr='get',
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[ast.Num(0), ast.Str('')], keywords=[],
|
||||
starargs=None, kwargs=None
|
||||
)
|
||||
return compile_expr(expr)
|
||||
|
||||
def compile_expr(expr):
|
||||
""" Compiles a purported Python expression to ast, verifies that it's safe
|
||||
(according to safe_eval's semantics) and alter its variable references to
|
||||
access qwebcontext data instead
|
||||
"""
|
||||
# string must be stripped otherwise whitespace before the start for
|
||||
# formatting purpose are going to break parse/compile
|
||||
st = ast.parse(expr.strip(), mode='eval')
|
||||
safe_eval.assert_valid_codeobj(
|
||||
safe_eval._SAFE_OPCODES,
|
||||
compile(st, '<>', 'eval'), # could be expr, but eval *should* be fine
|
||||
expr
|
||||
)
|
||||
|
||||
# ast.Expression().body -> expr
|
||||
return Contextifier().visit(st).body
|
||||
|
||||
def compile_format(f):
|
||||
""" Parses the provided format string and compiles it to a single
|
||||
expression ast, uses string concatenation via "+"
|
||||
"""
|
||||
elts = []
|
||||
base_idx = 0
|
||||
for m in _FORMAT_REGEX.finditer(f):
|
||||
literal = f[base_idx:m.start()]
|
||||
if literal:
|
||||
elts.append(ast.Str(literal if isinstance(literal, unicode) else literal.decode('utf-8')))
|
||||
|
||||
expr = m.group(1) or m.group(2)
|
||||
elts.append(compile_strexpr(expr))
|
||||
base_idx = m.end()
|
||||
# string past last regex match
|
||||
literal = f[base_idx:]
|
||||
if literal:
|
||||
elts.append(ast.Str(literal if isinstance(literal, unicode) else literal.decode('utf-8')))
|
||||
|
||||
return reduce(lambda acc, it: ast.BinOp(
|
||||
left=acc,
|
||||
op=ast.Add(),
|
||||
right=it
|
||||
), elts)
|
||||
|
||||
|
||||
_FORMAT_REGEX = re.compile(
|
||||
'(?:'
|
||||
# ruby-style pattern
|
||||
'#\{(.+?)\}'
|
||||
')|(?:'
|
||||
# jinja-style pattern
|
||||
'\{\{(.+?)\}\}'
|
||||
')')
|
||||
|
||||
|
||||
class QWebTemplateNotFound(QWebException):
|
||||
pass
|
||||
|
||||
|
||||
def eval_expr(expr, qwebcontext):
|
||||
try:
|
||||
if expr == "0":
|
||||
return qwebcontext.get(0, '')
|
||||
return qwebcontext.safe_eval(expr)
|
||||
except Exception:
|
||||
template = qwebcontext.get('__template__')
|
||||
raise_qweb_exception(message="Could not evaluate expression %r" % expr, expression=expr, template=template)
|
||||
|
||||
def eval_format_expr(expr, qwebcontext):
|
||||
expr, replacements = _FORMAT_REGEX.subn(
|
||||
lambda m: unicodifier(eval_expr(m.group(1) or m.group(2), qwebcontext)),
|
||||
expr
|
||||
)
|
||||
if replacements:
|
||||
return expr
|
||||
try:
|
||||
return str(expr % qwebcontext)
|
||||
except Exception:
|
||||
template = qwebcontext.get('__template__')
|
||||
raise_qweb_exception(message="Format error for expression %r" % expr, expression=expr, template=template)
|
||||
|
||||
|
||||
class FileSystemLoader(object):
|
||||
def __init__(self, path):
|
||||
# TODO: support multiple files #add_file() + add cache
|
||||
self.path = path
|
||||
self.doc = etree.parse(path).getroot()
|
||||
|
||||
def __iter__(self):
|
||||
for node in self.doc:
|
||||
name = node.get('t-name')
|
||||
if name:
|
||||
yield name
|
||||
|
||||
def __call__(self, name):
|
||||
for node in self.doc:
|
||||
if node.get('t-name') == name:
|
||||
root = etree.Element('templates')
|
||||
root.append(deepcopy(node))
|
||||
arch = etree.tostring(root, encoding='utf-8', xml_declaration=True)
|
||||
return arch
|
||||
|
||||
|
||||
class EvalDict(Mapping):
|
||||
""" Mapping proxying on an evaluation context, also builtins
|
||||
|
||||
* prevents access to cr and loader
|
||||
* acts as a defaultdict(lambda: None) so code can try to access unset
|
||||
variables and will just get None
|
||||
"""
|
||||
def __init__(self, ctx):
|
||||
self._ctx = ctx
|
||||
|
||||
def __iter__(self):
|
||||
return (k for k in self._ctx if k not in ('cr', 'loader'))
|
||||
|
||||
def __len__(self):
|
||||
l = len(self._ctx)
|
||||
if 'cr' in self._ctx:
|
||||
l -= 1
|
||||
if 'loader' in self._ctx:
|
||||
l -= 1
|
||||
return l
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key in ('cr', 'loader'):
|
||||
return None
|
||||
try:
|
||||
return self._ctx[key]
|
||||
except KeyError:
|
||||
return getattr(__builtin__, key, None)
|
||||
|
||||
|
||||
class QWebContext(dict):
|
||||
def __init__(self, env, data, loader=None, templates=None):
|
||||
self.env = env
|
||||
self.loader = loader or (lambda name: env['ir.ui.view'].read_template(name))
|
||||
super(QWebContext, self).__init__(data)
|
||||
self['defined'] = lambda key: key in self
|
||||
self.templates = templates or {}
|
||||
self.eval_dict = EvalDict(self)
|
||||
|
||||
# deprecated, use 'env' instead
|
||||
cr = property(lambda self: self.env.cr)
|
||||
uid = property(lambda self: self.env.uid)
|
||||
context = property(lambda self: self.env.context)
|
||||
|
||||
def safe_eval(self, expr):
|
||||
locals_dict = defaultdict(lambda: None)
|
||||
locals_dict.update(self)
|
||||
locals_dict.pop('cr', None)
|
||||
locals_dict.pop('loader', None)
|
||||
return safe_eval.safe_eval(expr, None, locals_dict, nocopy=True, locals_builtins=True)
|
||||
|
||||
def copy(self):
|
||||
""" Clone the current context, conserving all data and metadata
|
||||
(loader, template cache, ...)
|
||||
"""
|
||||
return QWebContext(self.env, dict.copy(self),
|
||||
loader=self.loader,
|
||||
templates=self.templates)
|
||||
|
||||
def __copy__(self):
|
||||
return self.copy()
|
||||
@@ -111,7 +111,7 @@ class TestQWeb(TransactionCase):
|
||||
ctx.update(params)
|
||||
result = doc.find('result[@id="{}"]'.format(template)).text
|
||||
self.assertEqual(
|
||||
qweb.render(template, qwebcontext=ctx).strip(),
|
||||
qweb.render(template, qwebcontext=ctx).strip().encode('utf-8'),
|
||||
(result or u'').strip().encode('utf-8'),
|
||||
template,
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
from openerp import api
|
||||
from openerp.tests import HttpCase
|
||||
from openerp.tests.common import TransactionCase
|
||||
from openerp.addons.base.ir.ir_qweb import AssetsBundle
|
||||
from openerp.addons.base.ir.ir_qweb import AssetsBundle, QWebContext
|
||||
from openerp.modules.module import get_resource_path
|
||||
|
||||
from collections import Counter
|
||||
@@ -18,6 +18,9 @@ class TestJavascriptAssetsBundle(TransactionCase):
|
||||
self.jsbundle_xmlid = 'test_assetsbundle.bundle1'
|
||||
self.cssbundle_xmlid = 'test_assetsbundle.bundle2'
|
||||
|
||||
def _get_asset(self, xmlid, env=None):
|
||||
return self.en['ir.qweb']._get_asset(xmlid, QWebContext(env or self.env, {}))
|
||||
|
||||
def _any_ira_for_bundle(self, type):
|
||||
""" Returns all ir.attachments associated to a bundle, regardless of the verion.
|
||||
"""
|
||||
@@ -30,7 +33,7 @@ class TestJavascriptAssetsBundle(TransactionCase):
|
||||
""" Checks that a bundle creates an ir.attachment record when its `js` method is called
|
||||
for the first time.
|
||||
"""
|
||||
self.bundle = AssetsBundle(self.jsbundle_xmlid, env=self.env)
|
||||
self.bundle = self._get_asset(self.jsbundle_xmlid, env=self.env)
|
||||
|
||||
# there shouldn't be any attachment associated to this bundle
|
||||
self.assertEquals(len(self._any_ira_for_bundle('js')), 0)
|
||||
@@ -47,7 +50,7 @@ class TestJavascriptAssetsBundle(TransactionCase):
|
||||
""" Checks that the bundle's cache is working, i.e. that the bundle creates only one
|
||||
ir.attachment record when rendered multiple times.
|
||||
"""
|
||||
bundle0 = AssetsBundle(self.jsbundle_xmlid, env=self.env)
|
||||
bundle0 = self._get_asset(self.jsbundle_xmlid)
|
||||
bundle0.js()
|
||||
|
||||
self.assertEquals(len(self._any_ira_for_bundle('js')), 1)
|
||||
@@ -56,7 +59,7 @@ class TestJavascriptAssetsBundle(TransactionCase):
|
||||
ira0 = self.registry['ir.attachment'].browse(self.cr, self.uid, self._any_ira_for_bundle('js')[0])
|
||||
date0 = ira0.create_date
|
||||
|
||||
bundle1 = AssetsBundle(self.jsbundle_xmlid, env=self.env)
|
||||
bundle1 = self._get_asset(self.jsbundle_xmlid)
|
||||
bundle1.js()
|
||||
|
||||
self.assertEquals(len(self._any_ira_for_bundle('js')), 1)
|
||||
@@ -71,7 +74,7 @@ class TestJavascriptAssetsBundle(TransactionCase):
|
||||
def test_03_date_invalidation(self):
|
||||
""" Checks that a bundle is invalidated when one of its assets' modification date is changed.
|
||||
"""
|
||||
bundle0 = AssetsBundle(self.jsbundle_xmlid, env=self.env)
|
||||
bundle0 = self._get_asset(self.jsbundle_xmlid)
|
||||
bundle0.js()
|
||||
last_modified0 = bundle0.last_modified
|
||||
version0 = bundle0.version
|
||||
@@ -79,7 +82,7 @@ class TestJavascriptAssetsBundle(TransactionCase):
|
||||
path = get_resource_path('test_assetsbundle', 'static', 'src', 'js', 'test_jsfile1.js')
|
||||
utime(path, None) # touch
|
||||
|
||||
bundle1 = AssetsBundle(self.jsbundle_xmlid, env=self.env)
|
||||
bundle1 = self._get_asset(self.jsbundle_xmlid)
|
||||
bundle1.js()
|
||||
last_modified1 = bundle1.last_modified
|
||||
version1 = bundle1.version
|
||||
@@ -93,7 +96,7 @@ class TestJavascriptAssetsBundle(TransactionCase):
|
||||
""" Checks that a bundle is invalidated when its content is modified by adding a file to
|
||||
source.
|
||||
"""
|
||||
bundle0 = AssetsBundle(self.jsbundle_xmlid, env=self.env)
|
||||
bundle0 = self._get_asset(self.jsbundle_xmlid)
|
||||
bundle0.js()
|
||||
html0 = bundle0.html
|
||||
version0 = bundle0.version
|
||||
@@ -115,7 +118,7 @@ class TestJavascriptAssetsBundle(TransactionCase):
|
||||
'inherit_id': bundle_id,
|
||||
})
|
||||
|
||||
bundle1 = AssetsBundle(self.jsbundle_xmlid, env=self.env(context={'check_view_ids': [newid]}))
|
||||
bundle1 = self._get_asset(self.jsbundle_xmlid, env=self.env(context={'check_view_ids': [newid]}))
|
||||
bundle1.js()
|
||||
html1 = bundle1.html
|
||||
version1 = bundle1.version
|
||||
@@ -129,7 +132,7 @@ class TestJavascriptAssetsBundle(TransactionCase):
|
||||
def test_05_debug(self):
|
||||
""" Checks that a bundle rendered in debug mode outputs non-minified assets.
|
||||
"""
|
||||
debug_bundle = AssetsBundle(self.jsbundle_xmlid, env=self.env)
|
||||
debug_bundle = self._get_asset(self.jsbundle_xmlid)
|
||||
content = debug_bundle.to_html(debug='assets')
|
||||
# find back one of the original asset file
|
||||
self.assertIn('/test_assetsbundle/static/src/js/test_jsfile1.js', content)
|
||||
@@ -143,21 +146,21 @@ class TestJavascriptAssetsBundle(TransactionCase):
|
||||
"""
|
||||
# note: changing the max_css_rules of a bundle does not invalidate its attachments
|
||||
# self.cssbundle_xlmid contains 3 rules
|
||||
self.bundle = AssetsBundle(self.cssbundle_xmlid, env=self.env, max_css_rules=1)
|
||||
self.bundle = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'max_css_rules': 1}))
|
||||
self.bundle.css()
|
||||
self.assertEquals(len(self._any_ira_for_bundle('css')), 3)
|
||||
self.assertEquals(len(self.bundle.get_attachments('css')), 3)
|
||||
|
||||
def test_07_paginated_css_generation2(self):
|
||||
# self.cssbundle_xlmid contains 3 rules
|
||||
self.bundle = AssetsBundle(self.cssbundle_xmlid, env=self.env, max_css_rules=2)
|
||||
self.bundle = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'max_css_rules': 2}))
|
||||
self.bundle.css()
|
||||
self.assertEquals(len(self._any_ira_for_bundle('css')), 2)
|
||||
self.assertEquals(len(self.bundle.get_attachments('css')), 2)
|
||||
|
||||
def test_08_paginated_css_generation3(self):
|
||||
# self.cssbundle_xlmid contains 3 rules
|
||||
self.bundle = AssetsBundle(self.cssbundle_xmlid, env=self.env, max_css_rules=3)
|
||||
self.bundle = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'max_css_rules': 3}))
|
||||
self.bundle.css()
|
||||
self.assertEquals(len(self._any_ira_for_bundle('css')), 1)
|
||||
self.assertEquals(len(self.bundle.get_attachments('css')), 1)
|
||||
@@ -166,7 +169,7 @@ class TestJavascriptAssetsBundle(TransactionCase):
|
||||
""" Checks that the bundle's cache is working, i.e. that a bundle creates only enough
|
||||
ir.attachment records when rendered multiple times.
|
||||
"""
|
||||
bundle0 = AssetsBundle(self.cssbundle_xmlid, env=self.env, max_css_rules=1)
|
||||
bundle0 = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'max_css_rules': 1}))
|
||||
bundle0.css()
|
||||
|
||||
self.assertEquals(len(self._any_ira_for_bundle('css')), 3)
|
||||
@@ -179,7 +182,7 @@ class TestJavascriptAssetsBundle(TransactionCase):
|
||||
ira2 = self.registry['ir.attachment'].browse(self.cr, self.uid, self._any_ira_for_bundle('css')[2])
|
||||
date2 = ira2.create_date
|
||||
|
||||
bundle1 = AssetsBundle(self.cssbundle_xmlid, env=self.env, max_css_rules=1)
|
||||
bundle1 = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'max_css_rules': 1}))
|
||||
bundle1.css()
|
||||
|
||||
self.assertEquals(len(self._any_ira_for_bundle('css')), 3)
|
||||
@@ -200,7 +203,7 @@ class TestJavascriptAssetsBundle(TransactionCase):
|
||||
def test_10_paginated_css_date_invalidation(self):
|
||||
""" Checks that a bundle is invalidated when one of its assets' modification date is changed.
|
||||
"""
|
||||
bundle0 = AssetsBundle(self.cssbundle_xmlid, env=self.env, max_css_rules=1)
|
||||
bundle0 = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'max_css_rules': 1}))
|
||||
bundle0.css()
|
||||
last_modified0 = bundle0.last_modified
|
||||
version0 = bundle0.version
|
||||
@@ -208,7 +211,7 @@ class TestJavascriptAssetsBundle(TransactionCase):
|
||||
path = get_resource_path('test_assetsbundle', 'static', 'src', 'css', 'test_cssfile1.css')
|
||||
utime(path, None) # touch
|
||||
|
||||
bundle1 = AssetsBundle(self.cssbundle_xmlid, env=self.env, max_css_rules=1)
|
||||
bundle1 = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'max_css_rules': 1}))
|
||||
bundle1.css()
|
||||
last_modified1 = bundle1.last_modified
|
||||
version1 = bundle1.version
|
||||
@@ -223,7 +226,7 @@ class TestJavascriptAssetsBundle(TransactionCase):
|
||||
""" Checks that a bundle is invalidated when its content is modified by adding a file to
|
||||
source.
|
||||
"""
|
||||
bundle0 = AssetsBundle(self.cssbundle_xmlid, env=self.env, max_css_rules=1)
|
||||
bundle0 = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'max_css_rules': 1}))
|
||||
bundle0.css()
|
||||
html0 = bundle0.html
|
||||
version0 = bundle0.version
|
||||
@@ -245,7 +248,7 @@ class TestJavascriptAssetsBundle(TransactionCase):
|
||||
'inherit_id': bundle_id,
|
||||
})
|
||||
|
||||
bundle1 = AssetsBundle(self.cssbundle_xmlid, env=self.env(context={'check_view_ids': [newid]}), max_css_rules=1)
|
||||
bundle1 = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'check_view_ids': [newid], 'max_css_rules': 1}))
|
||||
bundle1.css()
|
||||
html1 = bundle1.html
|
||||
version1 = bundle1.version
|
||||
@@ -259,7 +262,7 @@ class TestJavascriptAssetsBundle(TransactionCase):
|
||||
def test_12_paginated_css_debug(self):
|
||||
""" Check that a bundle in debug mode outputs non-minified assets.
|
||||
"""
|
||||
debug_bundle = AssetsBundle(self.cssbundle_xmlid, env=self.env, max_css_rules=1)
|
||||
debug_bundle = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'max_css_rules': 1}))
|
||||
content = debug_bundle.to_html(debug='assets')
|
||||
# find back one of the original asset file
|
||||
self.assertIn('/test_assetsbundle/static/src/css/test_cssfile1.css', content)
|
||||
@@ -269,7 +272,7 @@ class TestJavascriptAssetsBundle(TransactionCase):
|
||||
|
||||
def test_13_paginated_css_order(self):
|
||||
# self.cssbundle_xlmid contains 3 rules
|
||||
self.bundle = AssetsBundle(self.cssbundle_xmlid, env=self.env, max_css_rules=1)
|
||||
self.bundle = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'max_css_rules': 1}))
|
||||
stylesheets = self.bundle.css()
|
||||
|
||||
self.assertTrue(stylesheets[0].url.endswith('.0.css'))
|
||||
@@ -339,7 +342,7 @@ class TestAssetsBundleWithIRAMock(TransactionCase):
|
||||
|
||||
def _bundle(self, should_create, should_unlink):
|
||||
self.counter.clear()
|
||||
AssetsBundle(self.lessbundle_xmlid, env=self.env).to_html(debug='assets')
|
||||
self.en['ir.qweb']._get_asset(self.lessbundle_xmlid, QWebContext(self.env, {})).to_html(debug='assets')
|
||||
self.assertEquals(self.counter['create'], int(should_create))
|
||||
self.assertEquals(self.counter['unlink'], int(should_unlink))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user