[REF] *: add support for RTL languages

מזל טוב

The direction parameter will now have a real impact on the web interface and
completly switch the direction of the interface.
Add the dependency rtlcss to switch the direction with a css postprocessor.

Two different assets will be generated for ltr or rtl
/web/content/<...>/web.assets_backend.0.css
and
/web/content/<...>/rtl/web.assets_backend.0.css
to be able to differentiate it easily

Adapt the css and views to be easier to switch direction

Render graph as it is, do not move y-axis right side, as per mathematical rule
value goes left from origin are considered negative, so keep graph as it is
for right to left language

add test cases for language direction specific assets bundle
if language direction is rtl then we also store css files in assets bundle
now we store two assets bundle, one for ltr and one for rtl language direction
test two assets bundles generated, also test two assets bundle generated for en_US and ar_SY
This commit is contained in:
Martin Trigaux
2018-08-10 16:32:50 +02:00
parent b6f7ad3adb
commit f7130556bb
34 changed files with 507 additions and 79 deletions
@@ -342,3 +342,19 @@
}
}
}
// This is rtl language specific fix
// It will flip the fa-fa play icon in left direction
.o_rtl {
.o_reconciliation {
.o_reconciliation_line {
.toggle_match, .toggle_create {
transform: rotate(180deg);
transition: transform 300ms;
}
.visible_toggle, &[data-mode="match"] .toggle_match, &[data-mode="create"] .toggle_create {
transform: rotate(270deg);
}
}
}
}
+2 -2
View File
@@ -290,8 +290,8 @@
</div>
<div>
<field name='delivery_price' widget='monetary' class="oe_inline" options="{'currency_field': 'currency_id'}" nolabel="1" attrs="{'invisible': [('carrier_id','=', False)]}" force_save="1"/>
<button name="get_delivery_price" string="Get rate" type="object" class="oe_inline fa fa-arrow-right oe_link" attrs="{'invisible':['|',('carrier_id','=',False),('state','not in',('draft','sent'))]}"/>
<button name="set_delivery_line" string="Add to order" type="object" class="oe_inline fa fa-arrow-right oe_link" attrs="{'invisible':['|','|',('carrier_id','=',False),('state','not in',('draft','sent')),('delivery_rating_success','=',False)]}"/>
<button name="get_delivery_price" string="Get rate" type="object" class="oe_inline oe_link" attrs="{'invisible':['|',('carrier_id','=',False),('state','not in',('draft','sent'))]}"><i class="fa fa-arrow-right"/></button>
<button name="set_delivery_line" string="Add to order" type="object" class="oe_inline oe_link" attrs="{'invisible':['|','|',('carrier_id','=',False),('state','not in',('draft','sent')),('delivery_rating_success','=',False)]}"><i class="fa fa-arrow-right"/></button>
<field name='delivery_rating_success' invisible="1" force_save="1"/>
</div>
<div class="alert alert-info" role="status" attrs="{'invisible': ['|',('carrier_id','=', False),('delivery_message','=',False)]}">
@@ -230,7 +230,9 @@ var LivechatButton = Widget.extend({
};
this._chatWindow = new WebsiteLivechatWindow(this, this._livechat, options);
this._chatWindow.appendTo($('body')).then(function () {
self._chatWindow.$el.css({right: 0, bottom: 0});
var cssProps = {bottom: 0};
cssProps[_t.database.parameters.direction === 'rtl' ? 'left' : 'right'] = 0;
self._chatWindow.$el.css(cssProps);
self.$el.hide();
});
this._chatWindow.on('close', this, function () {
@@ -9,6 +9,7 @@ var core = require('web.core');
var utils = require('web.utils');
var QWeb = core.qweb;
var _t = core._t;
var THREAD_WINDOW_WIDTH = 325 + 5; // 5 pixels between windows
@@ -518,7 +519,9 @@ MailManager.include({
var index = 0;
while (index < count && index < this._threadWindows.length) {
var threadWindow = this._threadWindows[index];
threadWindow.$el.css({ right: THREAD_WINDOW_WIDTH*index, bottom: 0 });
var cssProps = {bottom: 0};
cssProps[_t.database.parameters.direction === 'rtl' ? 'left' : 'right'] = THREAD_WINDOW_WIDTH*index;
threadWindow.$el.css(cssProps);
threadWindow.do_show();
index++;
}
@@ -86,7 +86,8 @@ var AbstractThreadWindow = Widget.extend({
this._focusInput();
}
if (!config.device.isMobile) {
this.$el.css('margin-right', $.position.scrollbarWidth());
var margin_dir = _t.database.parameters.direction === "rtl" ? "margin-left" : "margin-right";
this.$el.css(margin_dir, $.position.scrollbarWidth());
}
var def = this.threadWidget.replace(this.$('.o_thread_window_content'));
return $.when(this._super(), def);
@@ -1,6 +1,7 @@
$o-mail-thread-window-zindex: $zindex-modal + 1 !default;
.o_thread_window {
direction: ltr;
display: flex;
flex-flow: column nowrap;
position: fixed;
+1
View File
@@ -27,6 +27,7 @@
font-size: 0.7em;
}
.o_mail_systray_dropdown {
direction: ltr;
width: 350px;
padding: 0;
@@ -110,6 +110,7 @@ td {
}
.pos {
direction: ltr;
padding: 0;
margin: 0;
background-color: #f0eeee;
@@ -1431,6 +1432,7 @@ td {
.pos .pos-receipt-container {
font-size: 0.75em;
text-align: center;
direction: ltr;
}
.pos .pos-sale-ticket {
@@ -6,6 +6,10 @@
<link rel="stylesheet" href="/point_of_sale/static/src/css/pos.css" id="pos-stylesheet"/>
<link rel="stylesheet" href="/point_of_sale/static/src/css/keyboard.css"/>
<t t-if="request and request.env['res.lang'].search([('code', '=', request.env.lang)]).direction == 'rtl'">
<link rel="stylesheet" type="text/less" href="/web/static/src/scss/fontawesome_overriden.scss"/>
</t>
<script type="text/javascript" src="/point_of_sale/static/lib/waitfont.js"></script>
<script type="text/javascript" src="/point_of_sale/static/src/js/main.js"></script>
<script type="text/javascript" src="/point_of_sale/static/src/js/db.js"></script>
+2 -1
View File
@@ -22,7 +22,8 @@
</t>
<t t-set="head" t-value="head_website + (head or '')"/>
<div id="wrapwrap" t-att-class="pageName or ''">
<t t-set="direction" t-value="request.env['res.lang'].search([('code', '=', request.env.lang)]).direction"/>
<div id="wrapwrap" t-att-class="(pageName or '') + ' ' + (direction == 'rtl' and 'o_rtl' or '')">
<header>
<nav class="navbar navbar-expand-md navbar-light bg-light">
<div class="container">
@@ -111,6 +111,7 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi
return session.is_bound
.then(function () {
self.$el.toggleClass('o_rtl', _t.database.parameters.direction === "rtl");
self.bind_events();
return $.when(
self.set_action_manager(),
+6 -4
View File
@@ -22,6 +22,7 @@ odoo.define('web.dom', function (require) {
var config = require('web.config');
var core = require('web.core');
var _t = core._t;
/**
* Private function to notify that something has been attached in the DOM
@@ -90,14 +91,15 @@ return {
minHeight = (options && options.min_height) || 50;
$fixedTextarea = $('<textarea disabled>', {
class: $textarea[0].className,
}).css({
class: $textarea[0].className,});
var direction = _t.database.parameters.direction === 'rtl' ? 'right' : 'left';
$fixedTextarea.css({
position: 'absolute',
opacity: 0,
height: 10,
top: -10000,
left: -10000,
});
}).css(direction, -10000);
$fixedTextarea.data("auto_resize", true);
var style = window.getComputedStyle($textarea[0], null);
@@ -413,6 +413,7 @@ return AbstractRenderer.extend({
},
height: 'parent',
unselectAuto: false,
isRTL: _t.database.parameters.direction === "rtl",
});
this.$calendar.fullCalendar(fc_options);
@@ -164,8 +164,11 @@ var PivotController = AbstractController.extend({
fields: fields
}));
var cssProps = {top: top};
cssProps[_t.database.parameters.direction === 'rtl' ? 'right' : 'left'] =
_t.database.parameters.direction === 'rtl' ? this.$el.width() - left : left;
this.$fieldSelection.find('.dropdown-menu').first()
.css({top: top, left: left})
.css(cssProps)
.addClass('show');
},
/**
@@ -79,3 +79,15 @@ div.dropdown-menu.bootstrap-datetimepicker-widget{
}
}
}
// This is rtl language specific fix
// it will fix the user agent stylesheet padding
// and transform the prev and next icons
.o_rtl {
.bootstrap-datetimepicker-widget .list-unstyled {
padding: 0;
.prev , .next {
transform: rotate(180deg);
}
}
}
+6
View File
@@ -81,3 +81,9 @@
.navbar-nav {
@include dropdown-menu-mixin;
}
.o_rtl {
.dropdown-menu {
left: auto;
}
}
@@ -0,0 +1,36 @@
// This is rtl language specific fix
// It will override the font awesome symbols and flip them 180 degree
.fa {
&.fa-align-right,
&.fa-align-left,
&.fa-chevron-right,
&.fa-chevron-left,
&.fa-arrow-right,
&.fa-arrow-left,
&.fa-hand-o-right,
&.fa-hand-o-left,
&.fa-arrow-circle-right,
&.fa-arrow-circle-left,
&.fa-caret-right,
&.fa-caret-left,
&.fa-rotate-right,
&.fa-rotate-left,
&.fa-angle-double-right,
&.fa-angle-double-left,
&.fa-angle-right,
&.fa-angle-left,
&.fa-quote-right,
&.fa-quote-left,
&.fa-chevron-circle-right,
&.fa-chevron-circle-left,
&.fa-long-arrow-right,
&.fa-long-arrow-left,
&.fa-toggle-right,
&.fa-toggle-left,
&.fa-caret-square-o-right,
&.fa-arrow-circle-o-left,
&.fa-arrow-circle-o-right,
&.fa-caret-square-o-left {
transform: rotate(180deg);
}
}
@@ -1,5 +1,6 @@
.o_graph {
.o_graph_svg_container {
direction: ltr#{"/*rtl:ignore*/"};
height: 100%;
overflow: auto;
svg {
+1
View File
@@ -1,5 +1,6 @@
html, body {
height: 100%;
direction: ltr;
}
body {
@@ -36,6 +36,7 @@
}
.o_facet_values {
direction: ltr#{"/*rtl:ignore*/"};
padding: 0 $o-searchview-facet-remove-width 0 5px;
.o_facet_values_sep {
font-style: italic;
+16
View File
@@ -23,3 +23,19 @@
}
}
}
// This is rtl language specific fix
// It will fix the extra space in ui-autocomplete class
// and flip the next and previous symbols of jquery ui.
.o_rtl {
.ui-autocomplete {
direction: ltr;
right: 0;
left: auto;
}
.ui-datepicker-next, .ui-datepicker-prev {
-webkit-transform: rotate(180deg);
-o-transform: rotate(180deg);
transform: rotate(180deg);
}
}
@@ -6,6 +6,7 @@
// General
// ------------------------------------------------------------------
.o_web_client {
direction: ltr;
position: relative; // normally useless but required by bootstrap-datepicker
background-color: $o-webclient-background-color;
}
@@ -44,3 +44,9 @@ select {
@include o-text-overflow;
max-width: 150px;
}
.o_rtl {
.navbar-right {
padding: 0; // fix for user agent stylesheet -webkit-padding-start: 40px in rtl language
}
}
+4
View File
@@ -62,6 +62,10 @@
<link rel="stylesheet" type="text/scss" href="/web/static/src/scss/banner.scss"/>
<link rel="stylesheet" type="text/scss" href="/web/static/src/scss/colorpicker.scss"/>
<t t-if="request and request.env['res.lang'].search([('code', '=', request.env.lang)]).direction == 'rtl'">
<link rel="stylesheet" type="text/less" href="/web/static/src/scss/fontawesome_overriden.scss"/>
</t>
<script type="text/javascript" src="/web/static/lib/es5-shim/es5-shim.min.js"></script>
<script type="text/javascript" src="/web/static/lib/underscore/underscore.js"></script>
<script type="text/javascript" src="/web/static/lib/underscore.string/lib/underscore.string.js"></script>
+2 -1
View File
@@ -59,7 +59,8 @@
</script>
<t t-raw="head or ''"/>
</head>
<body id="web_editor_inside_iframe" onload="var fn=window.top.odoo &amp;&amp; window.top.odoo[callback+'_content']; if(fn) {fn();} else {console.warn('Please don\'t open iframe without the odoo backend');}">
<t t-set="direction" t-value="request.env['res.lang'].search([('code', '=', request.env.lang)]).direction"/>
<body id="web_editor_inside_iframe" onload="var fn=window.top.odoo &amp;&amp; window.top.odoo[callback+'_content']; if(fn) {fn();} else {console.warn('Please don\'t open iframe without the odoo backend');}" t-attf-style="direction: #{direction};">
<div id="wrapwrap">
<main>
<t t-raw="0"/>
+14 -5
View File
@@ -4,6 +4,7 @@ odoo.define('web_tour.Tip', function(require) {
var config = require('web.config');
var core = require('web.core');
var Widget = require('web.Widget');
var _t = core._t;
var Tip = Widget.extend({
template: "Tip",
@@ -139,19 +140,27 @@ var Tip = Widget.extend({
if (this.tip_opened) return;
this.$el.removeClass("o_animated");
// Reverse left/right position if direction is right to left
var appendAt = this.info.position;
if (_t.database.parameters.direction === 'rtl') {
appendAt = appendAt === 'right' ? 'left': 'right';
}
this.$el.position({
my: this._get_spaced_inverted_position(this.info.position),
at: this.info.position,
my: this._get_spaced_inverted_position(appendAt),
at: appendAt,
of: this.$anchor,
collision: "none",
});
// Reverse overlay if direction is right to left
var positionRight = _t.database.parameters.direction === 'rtl' ? "right" : "left";
var positionLeft = _t.database.parameters.direction === 'rtl' ? "left" : "right";
var offset = this.$el.offset();
this.$tooltip_overlay.css({
top: -Math.min((this.info.position === "bottom" ? this.info.space : this.info.overlay.y), offset.top),
right: -Math.min((this.info.position === "left" ? this.info.space : this.info.overlay.x), this.$window.width() - (offset.left + this.init_width + this.double_border_width)),
right: -Math.min((this.info.position === positionRight ? this.info.space : this.info.overlay.x), this.$window.width() - (offset.left + this.init_width + this.double_border_width)),
bottom: -Math.min((this.info.position === "top" ? this.info.space : this.info.overlay.y), this.$window.height() - (offset.top + this.init_height + this.double_border_width)),
left: -Math.min((this.info.position === "right" ? this.info.space : this.info.overlay.x), offset.left),
left: -Math.min((this.info.position === positionLeft ? this.info.space : this.info.overlay.x), offset.left),
});
this.position = this.$el.position();
@@ -217,7 +226,7 @@ var Tip = Widget.extend({
} else {
overflow = (offset.top + this.content_height + this.double_border_width + this.info.overlay.y > this.$window.height());
}
if (posVertical && overflow || this.info.position === "left") {
if (posVertical && overflow || this.info.position === "left" || (_t.database.parameters.direction === 'rtl' && this.info.position == "right")) {
mbLeft -= (this.content_width - this.init_width);
}
if (!posVertical && overflow || this.info.position === "top") {
+13 -2
View File
@@ -54,7 +54,9 @@
$o-tip-arrow-delay-out: floor($o-tip-duration-out / 2);
box-sizing: content-box;
/*rtl:begin:ignore*/
@include o-position-absolute($top: 50%, $left: 50%);
/*rtl:end:ignore*/
opacity: 0;
width: $o-tip-size;
height: $o-tip-size;
@@ -89,7 +91,9 @@
border-right-color: $o-tip-color;
}
&.top::before {
/*rtl:begin:ignore*/
@include o-position-absolute($bottom: -$o-tip-arrow-length+$o-tip-arrow-integration, $left: ($o-tip-size - $o-tip-arrow-width) / 2);
/*rtl:end:ignore*/
border-width: $o-tip-arrow-length $o-tip-arrow-width/2 0 $o-tip-arrow-width/2;
border-top-color: $o-tip-color;
}
@@ -99,7 +103,9 @@
border-left-color: $o-tip-color;
}
&.bottom::before {
/*rtl:begin:ignore*/
@include o-position-absolute($top: -$o-tip-arrow-length+$o-tip-arrow-integration, $left: ($o-tip-size - $o-tip-arrow-width) / 2);
/*rtl:end:ignore*/
border-width: 0 $o-tip-arrow-width/2 $o-tip-arrow-length $o-tip-arrow-width/2;
border-bottom-color: $o-tip-color;
}
@@ -169,6 +175,7 @@
display: block;
}
> .o_tooltip_content {
direction: ltr;
visibility: visible;
transition: visibility 0s ease $o-tip-duration-in;
}
@@ -191,7 +198,9 @@
border-top-color: $o-tip-color;
}
&::after {
/*rtl:begin:ignore*/
@include o-position-absolute($bottom: -$o-tip-arrow-length+$o-tip-border-width*2, $left: ($o-tip-size - $o-tip-arrow-width) / 2);
/*rtl:end:ignore*/
border-width: $o-tip-arrow-length $o-tip-arrow-width/2 0 $o-tip-arrow-width/2;
border-top-color: white;
transition: border-top-color $o-tip-white-arrow-duration-in ease $o-tip-white-arrow-delay-in;
@@ -215,7 +224,9 @@
border-bottom-color: $o-tip-color;
}
&::after {
/*rtl:begin:ignore*/
@include o-position-absolute($top: -$o-tip-arrow-length+$o-tip-border-width*2, $left: ($o-tip-size - $o-tip-arrow-width) / 2);
/*rtl:end:ignore*/
border-width: 0 $o-tip-arrow-width/2 $o-tip-arrow-length $o-tip-arrow-width/2;
border-bottom-color: white;
transition: border-bottom-color $o-tip-white-arrow-duration-in ease $o-tip-white-arrow-delay-in;
@@ -232,8 +243,8 @@
}
&.top, &.bottom {
&::before, &.active::after {
left: auto;
right: ($o-tip-size - $o-tip-arrow-width) / 2;
left: auto#{"/*rtl:ignore*/"};
right: ($o-tip-size - $o-tip-arrow-width) / 2#{"/*rtl:ignore*/"};
}
}
}
@@ -166,6 +166,7 @@
}
.o_graph_sales {
direction: ltr#{"/*rtl:ignore*/"};
.o_legend0 {
background-color: $o-brand-primary;
}
@@ -5,6 +5,7 @@
// LAYOUTING
body.o_connected_user {
direction: ltr;
padding-top: $o-navbar-height!important;
}
+3 -3
View File
@@ -158,8 +158,8 @@
<h3 class="card-header">About This Community</h3>
<div class="card-body">
<span t-field="forum.description"/>
<a t-if="not header.get('is_guidelines')" t-attf-href="/forum/#{slug(forum)}/faq" class="fa fa-arrow-right"> Read Guidelines</a>
<a t-if="header.get('is_guidelines')" t-attf-href="/forum/#{slug(forum)}" class="fa fa-arrow-right"> Back to <span t-field="forum.name"/></a>
<a t-if="not header.get('is_guidelines')" t-attf-href="/forum/#{slug(forum)}/faq"><i class="fa fa-arrow-right"/> Read Guidelines</a>
<a t-if="header.get('is_guidelines')" t-attf-href="/forum/#{slug(forum)}"><i class="fa fa-arrow-right"/> Back to <span t-field="forum.name"/></a>
</div>
</div>
<div class="card" id="moderation_tools" t-if="user.karma>=forum.karma_moderate">
@@ -1413,7 +1413,7 @@
</div>
<t t-if="uid == user.id">
<div>
<a class="fa fa-arrow-right" t-attf-href="/forum/#{slug(forum)}/user/#{slug(user)}/edit">Edit Your Profile</a>
<a t-attf-href="/forum/#{slug(forum)}/user/#{slug(user)}/edit"><i class="fa fa-arrow-right"/>Edit Your Profile</a>
</div>
</t>
</div>
+3 -3
View File
@@ -1,7 +1,7 @@
"id","name","code","iso_code","direction","grouping","decimal_point","thousands_sep","date_format","time_format","translatable","week_start"
"base.lang_en","English","en_US","en","Left-to-Right","[3,0]",".",",","%m/%d/%Y","%H:%M:%S","True","7"
"base.lang_am_ET","Amharic / አምሃርኛ","am_ET","am_ET","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%I:%M:%S","True","7"
"base.lang_ar_SY","Arabic / الْعَرَبيّة","ar_SY","ar","Left-to-Right","[3,0]",".",",","%d %b, %Y","%I:%M:%S","True","6"
"base.lang_ar_SY","Arabic / الْعَرَبيّة","ar_SY","ar","Right-to-Left","[3,0]",".",",","%d %b, %Y","%I:%M:%S","True","6"
"base.lang_eu_ES","Basque / Euskara","eu_ES","eu_ES","Left-to-Right","[]",",",,"%a, %Y.eko %bren %da","%H:%M:%S","True","1"
"base.lang_bs_BA","Bosnian / bosanski jezik","bs_BA","bs","Left-to-Right","[]",",",,"%d.%m.%Y","%H:%M:%S","True","7"
"base.lang_bg","Bulgarian / български език","bg_BG","bg","Left-to-Right","[3,0]",",",,"%d.%m.%Y","%H,%M,%S","True","1"
@@ -29,7 +29,7 @@
base.lang_de_CH,"German (CH) / Deutsch (CH)",de_CH,de_CH,"Left-to-Right","[3,3]",".","'","%d.%m.%Y","%H:%M:%S","True","1"
"base.lang_el_GR","Greek / Ελληνικά","el_GR","el_GR","Left-to-Right","[]",",",".","%d/%m/%Y","%I:%M:%S %p","True","1"
"base.lang_gu_IN","Gujarati / ગુજરાતી","gu_IN","gu","Left-to-Right","[]",".",",","%A %d %b %Y","%I:%M:%S","True","7"
"base.lang_he_IL","Hebrew / עִבְרִי","he_IL","he","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","True","7"
"base.lang_he_IL","Hebrew / עִבְרִי","he_IL","he","Right-to-Left","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","True","7"
"base.lang_hi_IN","Hindi / हिंदी","hi_IN","hi","Left-to-Right","[]",".",",","%A %d %b %Y","%I:%M:%S","True","7"
"base.lang_hu","Hungarian / Magyar","hu_HU","hu","Left-to-Right","[3,0]",",",".","%Y-%m-%d","%H.%M.%S","True","1"
"base.lang_id","Indonesian / Bahasa Indonesia","id_ID","id","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","True","7"
@@ -46,7 +46,7 @@ base.lang_de_CH,"German (CH) / Deutsch (CH)",de_CH,de_CH,"Left-to-Right","[3,3]"
"base.lang_mn","Mongolian / монгол","mn_MN","mn","Left-to-Right","[3,0]",",",".","%Y.%m.%d","%H:%M:%S","True","7"
base.lang_my,"Burmese / ဗမာစာ",my_MM,my,"Left-to-Right","[3,3]",".",",","%Y %b %d %A","%I:%M:%S %p","True","7"
"base.lang_nb_NO","Norwegian Bokmål / Norsk bokmål","nb_NO","nb_NO","Left-to-Right","[3,0]",","," ","%d. %b %Y","%H:%M:%S","True","1"
"base.lang_fa_IR","Persian / فارس","fa_IR","fa","Left-to-Right","[3,0]",".",",","%Y/%m/%d","%H:%M:%S","True","6"
"base.lang_fa_IR","Persian / فارس","fa_IR","fa","Right-to-Left","[3,0]",".",",","%Y/%m/%d","%H:%M:%S","True","6"
"base.lang_pl","Polish / Język polski","pl_PL","pl","Left-to-Right","[]",",",,"%d.%m.%Y","%H:%M:%S","True","1"
"base.lang_pt_BR","Portuguese (BR) / Português (BR)","pt_BR","pt_BR","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","True","7"
"base.lang_pt","Portuguese / Português","pt_PT","pt","Left-to-Right","[]",",",,"%d-%m-%Y","%H:%M:%S","True","1"
1 id name code iso_code direction grouping decimal_point thousands_sep date_format time_format translatable week_start
2 base.lang_en English en_US en Left-to-Right [3,0] . , %m/%d/%Y %H:%M:%S True 7
3 base.lang_am_ET Amharic / አምሃርኛ am_ET am_ET Left-to-Right [3,0] . , %d/%m/%Y %I:%M:%S True 7
4 base.lang_ar_SY Arabic / الْعَرَبيّة ar_SY ar Left-to-Right Right-to-Left [3,0] . , %d %b, %Y %I:%M:%S True 6
5 base.lang_eu_ES Basque / Euskara eu_ES eu_ES Left-to-Right [] , %a, %Y.eko %bren %da %H:%M:%S True 1
6 base.lang_bs_BA Bosnian / bosanski jezik bs_BA bs Left-to-Right [] , %d.%m.%Y %H:%M:%S True 7
7 base.lang_bg Bulgarian / български език bg_BG bg Left-to-Right [3,0] , %d.%m.%Y %H,%M,%S True 1
29 base.lang_de_CH German (CH) / Deutsch (CH) de_CH de_CH Left-to-Right [3,3] . ' %d.%m.%Y %H:%M:%S True 1
30 base.lang_el_GR Greek / Ελληνικά el_GR el_GR Left-to-Right [] , . %d/%m/%Y %I:%M:%S %p True 1
31 base.lang_gu_IN Gujarati / ગુજરાતી gu_IN gu Left-to-Right [] . , %A %d %b %Y %I:%M:%S True 7
32 base.lang_he_IL Hebrew / עִבְרִי he_IL he Left-to-Right Right-to-Left [3,0] . , %d/%m/%Y %H:%M:%S True 7
33 base.lang_hi_IN Hindi / हिंदी hi_IN hi Left-to-Right [] . , %A %d %b %Y %I:%M:%S True 7
34 base.lang_hu Hungarian / Magyar hu_HU hu Left-to-Right [3,0] , . %Y-%m-%d %H.%M.%S True 1
35 base.lang_id Indonesian / Bahasa Indonesia id_ID id Left-to-Right [3,0] , . %d/%m/%Y %H:%M:%S True 7
46 base.lang_mn Mongolian / монгол mn_MN mn Left-to-Right [3,0] , . %Y.%m.%d %H:%M:%S True 7
47 base.lang_my Burmese / ဗမာစာ my_MM my Left-to-Right [3,3] . , %Y %b %d %A %I:%M:%S %p True 7
48 base.lang_nb_NO Norwegian Bokmål / Norsk bokmål nb_NO nb_NO Left-to-Right [3,0] ,   %d. %b %Y %H:%M:%S True 1
49 base.lang_fa_IR Persian / فارس fa_IR fa Left-to-Right Right-to-Left [3,0] . , %Y/%m/%d %H:%M:%S True 6
50 base.lang_pl Polish / Język polski pl_PL pl Left-to-Right [] , %d.%m.%Y %H:%M:%S True 1
51 base.lang_pt_BR Portuguese (BR) / Português (BR) pt_BR pt_BR Left-to-Right [3,0] , . %d/%m/%Y %H:%M:%S True 7
52 base.lang_pt Portuguese / Português pt_PT pt Left-to-Right [] , %d-%m-%Y %H:%M:%S True 1
+128 -49
View File
@@ -98,15 +98,18 @@ class AssetsBundle(object):
self.css_errors = []
self._checksum = None
self.files = files
self.user_direction = self.env['res.lang'].search(
[('code', '=', (self.env.context.get('lang') or self.env.user.lang))]
).direction
for f in files:
if f['atype'] == 'text/sass':
self.stylesheets.append(SassStylesheetAsset(self, url=f['url'], filename=f['filename'], inline=f['content'], media=f['media']))
self.stylesheets.append(SassStylesheetAsset(self, url=f['url'], filename=f['filename'], inline=f['content'], media=f['media'], direction=self.user_direction))
elif f['atype'] == 'text/scss':
self.stylesheets.append(ScssStylesheetAsset(self, url=f['url'], filename=f['filename'], inline=f['content'], media=f['media']))
self.stylesheets.append(ScssStylesheetAsset(self, url=f['url'], filename=f['filename'], inline=f['content'], media=f['media'], direction=self.user_direction))
elif f['atype'] == 'text/less':
self.stylesheets.append(LessStylesheetAsset(self, url=f['url'], filename=f['filename'], inline=f['content'], media=f['media']))
self.stylesheets.append(LessStylesheetAsset(self, url=f['url'], filename=f['filename'], inline=f['content'], media=f['media'], direction=self.user_direction))
elif f['atype'] == 'text/css':
self.stylesheets.append(StylesheetAsset(self, url=f['url'], filename=f['filename'], inline=f['content'], media=f['media']))
self.stylesheets.append(StylesheetAsset(self, url=f['url'], filename=f['filename'], inline=f['content'], media=f['media'], direction=self.user_direction))
elif f['atype'] == 'text/javascript':
self.javascripts.append(JavascriptAsset(self, url=f['url'], filename=f['filename'], inline=f['content']))
@@ -211,7 +214,10 @@ class AssetsBundle(object):
"""
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/%-%/{0}{1}%.{2}'.format( # The wilcards are id, version and pagination number (if any)
('rtl/' if type == 'css' and self.user_direction == 'rtl' else ''),
self.name,
type)),
'!', ('url', '=like', '/web/content/%-{}/%'.format(self.version))
]
@@ -229,7 +235,13 @@ class AssetsBundle(object):
by file name and only return the one with the max id for each group.
"""
version = "%" if ignore_version else self.version
url_pattern = '/web/content/%-{0}/{1}{2}.{3}'.format(version, self.name, '.%' if type == 'css' else '', type)
url_pattern = '/web/content/%-{0}/{1}{2}{3}.{4}'.format(
version,
('rtl/' if type == 'css' and self.user_direction == 'rtl' else ''),
self.name,
'.%' if type == 'css' else '',
type
)
self.env.cr.execute("""
SELECT max(id)
FROM ir_attachment
@@ -245,7 +257,15 @@ class AssetsBundle(object):
assert type in ('js', 'css')
ira = self.env['ir.attachment']
fname = '%s%s.%s' % (self.name, ('' if inc is None else '.%s' % inc), type)
# Set user direction in name to store two bundles
# 1 for ltr and 1 for rtl, this will help during cleaning of assets bundle
# and allow to only clear the current direction bundle
# (this applies to css bundles only)
fname = '%s%s.%s' % (
self.name,
('' if inc is None else '.%s' % inc),
type
)
mimetype = 'application/javascript' if type == 'js' else 'text/css'
values = {
'name': "/web/content/%s" % type,
@@ -259,7 +279,7 @@ class AssetsBundle(object):
}
attachment = ira.sudo().create(values)
url = '/web/content/%s-%s/%s' % (attachment.id, self.version, fname)
url = '/web/content/%s-%s%s/%s' % (attachment.id, self.version, ('/rtl' if type == 'css' and self.user_direction == 'rtl' else ''), fname)
values = {
'name': url,
'url': url,
@@ -351,7 +371,11 @@ class AssetsBundle(object):
def is_css_preprocessed(self):
preprocessed = True
attachments = None
for atype in (SassStylesheetAsset, ScssStylesheetAsset, LessStylesheetAsset):
asset_types = [SassStylesheetAsset, ScssStylesheetAsset, LessStylesheetAsset]
if self.user_direction == 'rtl':
asset_types.append(StylesheetAsset)
for atype in asset_types:
outdated = False
assets = dict((asset.html_url, asset) for asset in self.stylesheets if isinstance(asset, atype))
if assets:
@@ -378,47 +402,57 @@ class AssetsBundle(object):
def preprocess_css(self, debug=False, old_attachments=None):
"""
Checks if the bundle contains any sass/less content, then compiles it to css.
If user language direction is Right to Left then consider css files to call run_rtlcss,
css files are also stored in ir.attachment after processing done by rtlcss.
Returns the bundle's flat css.
"""
for atype in (SassStylesheetAsset, ScssStylesheetAsset, LessStylesheetAsset):
assets = [asset for asset in self.stylesheets if isinstance(asset, atype)]
if assets:
source = '\n'.join([asset.get_source() for asset in assets])
compiled = self.compile_css(assets[0].compile, source)
if self.stylesheets:
compiled = ""
for atype in (SassStylesheetAsset, ScssStylesheetAsset, LessStylesheetAsset):
assets = [asset for asset in self.stylesheets if isinstance(asset, atype)]
if assets:
source = '\n'.join([asset.get_source() for asset in assets])
compiled = self.compile_css(assets[0].compile, source)
if not self.css_errors and old_attachments:
old_attachments.unlink()
# We want to run rtlcss on normal css, so merge it in compiled
if self.user_direction == 'rtl':
stylesheet_assets = [asset for asset in self.stylesheets if not isinstance(asset, (SassStylesheetAsset, ScssStylesheetAsset, LessStylesheetAsset))]
compiled += '\n'.join([asset.get_source() for asset in stylesheet_assets])
compiled = self.run_rtlcss(compiled)
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 not self.css_errors and old_attachments:
old_attachments.unlink()
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=base64.b64encode(asset.content.encode('utf8')),
mimetype='text/css',
type='binary',
name=url,
url=url,
datas_fname=fname,
res_model=False,
res_id=False,
))
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 self.env.context.get('commit_assetsbundle') is True:
self.env.cr.commit()
except psycopg2.Error:
pass
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=base64.b64encode(asset.content.encode('utf8')),
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)
@@ -459,6 +493,41 @@ class AssetsBundle(object):
return compiled
def run_rtlcss(self, source):
rtlcss = 'rtlcss'
cmd = [rtlcss, '-']
try:
rtlcss = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
except Exception:
# Check the presence of rtlcss, if rtlcss not available then we should return normal less file
try:
process = Popen(
['rtlcss', '--version'], stdout=PIPE, stderr=PIPE
)
except (OSError, IOError):
_logger.warning('You need rtlcss to convert css file to right to left compatiblity.')
return source
msg = "Could not execute command %r" % cmd[0]
_logger.error(msg)
self.css_errors.append(msg)
return ''
result = rtlcss.communicate(input=source.encode('utf-8'))
if rtlcss.returncode:
cmd_output = ''.join(misc.ustr(result))
if not cmd_output:
cmd_output = "Process exited with return code %d\n" % rtlcss.returncode
error = self.get_rtlcss_error(cmd_output, source=source)
_logger.warning(error)
self.css_errors.append(error)
return ''
rtlcss_result = result[0].strip().decode('utf8')
return rtlcss_result
def get_preprocessor_error(self, stderr, source=None):
"""Improve and remove sensitive information from sass/less compilator error messages"""
error = misc.ustr(stderr).split('Load paths')[0].replace(' Use --trace for backtrace.', '')
@@ -471,6 +540,11 @@ class AssetsBundle(object):
error += '\n - %s' % (asset.url if asset.url else '<inline sass>')
return error
def get_rtlcss_error(self, stderr, source=None):
"""Improve and remove sensitive information from sass/less compilator error messages"""
error = misc.ustr(stderr).split('Load paths')[0].replace(' Use --trace for backtrace.', '')
error += "This error occured while compiling the bundle '%s' containing:" % self.name
return error
class WebAsset(object):
html_url_format = '%s'
@@ -607,7 +681,12 @@ class StylesheetAsset(WebAsset):
def __init__(self, *args, **kw):
self.media = kw.pop('media', None)
self.direction = kw.pop('direction', None)
super(StylesheetAsset, self).__init__(*args, **kw)
if self.direction == 'rtl' and self.url:
self.html_url_args = self.url.rsplit('.', 1)
self.html_url_format = '%%s/%s/%s.%%s' % ('rtl', self.bundle.name)
self.html_url_args = tuple(self.html_url_args)
@property
def content(self):
@@ -642,6 +721,10 @@ class StylesheetAsset(WebAsset):
self.bundle.css_errors.append(str(e))
return ''
def get_source(self):
content = self.inline or self._fetch_content()
return "/*! %s */\n%s" % (self.id, content)
def minify(self):
# remove existing sourcemaps, make no sense after re-mini
content = self.rx_sourceMap.sub('', self.content)
@@ -674,12 +757,8 @@ class PreprocessedCSS(StylesheetAsset):
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 get_source(self):
content = self.inline or self._fetch_content()
return "/*! %s */\n%s" % (self.id, content)
self.html_url_format = '%%s/%s%s/%%s.css' % ('rtl/' if self.direction == 'rtl' else '', self.bundle.name)
def get_command(self):
raise NotImplementedError
@@ -0,0 +1,3 @@
.rule4 {
color: green;
}
@@ -34,18 +34,20 @@ class TestJavascriptAssetsBundle(TransactionCase):
super(TestJavascriptAssetsBundle, self).setUp()
self.jsbundle_xmlid = 'test_assetsbundle.bundle1'
self.cssbundle_xmlid = 'test_assetsbundle.bundle2'
self.env['res.lang'].load_lang('ar_SY')
def _get_asset(self, xmlid, env=None):
env = (env or self.env)
files, remains = env['ir.qweb']._get_asset_content(xmlid, env.context)
return AssetsBundle(xmlid, files, env=env)
def _any_ira_for_bundle(self, type):
def _any_ira_for_bundle(self, type, lang=None):
""" Returns all ir.attachments associated to a bundle, regardless of the verion.
"""
user_direction = self.env['res.lang'].search([('code', '=', (lang or self.env.user.lang))]).direction
bundle = self.jsbundle_xmlid if type == 'js' else self.cssbundle_xmlid
return self.env['ir.attachment'].search([
('url', '=like', '/web/content/%-%/{0}%.{1}'.format(bundle, type))
('url', '=like', '/web/content/%-%/{0}{1}%.{2}'.format(('rtl/' if type == 'css' and user_direction == 'rtl' else ''), bundle, type))
])
def test_01_generation(self):
@@ -312,7 +314,207 @@ class TestJavascriptAssetsBundle(TransactionCase):
content = bundle0.to_html()
self.assertEqual(content.count('test_assetsbundle.bundle2.0.css'), 1)
def test_15_exteral_lib_assets(self):
# Language direction specific tests
def test_15_rtl_css_generation(self):
""" Checks that a bundle creates an ir.attachment record when its `css` method is called
for the first time for language with different direction and separate bundle is created for rtl direction.
"""
self.bundle = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'lang': 'ar_SY'}))
# there shouldn't be any attachment associated to this bundle
self.assertEquals(len(self._any_ira_for_bundle('css', lang='ar_SY')), 0)
self.assertEquals(len(self.bundle.get_attachments('css')), 0)
# trigger the first generation and, thus, the first save in database
self.bundle.css()
# there should be one attachment associated to this bundle
self.assertEquals(len(self._any_ira_for_bundle('css', lang='ar_SY')), 1)
self.assertEquals(len(self.bundle.get_attachments('css')), 1)
def test_16_ltr_and_rtl_css_access(self):
""" Checks that the bundle's cache is working, i.e. that the bundle creates only one
ir.attachment record when rendered multiple times for rtl direction also check we have two css bundles,
one for ltr and one for rtl.
"""
# Assets access for en_US language
ltr_bundle0 = self._get_asset(self.cssbundle_xmlid)
ltr_bundle0.css()
self.assertEquals(len(self._any_ira_for_bundle('css')), 1)
ltr_version0 = ltr_bundle0.version
ltr_ira0 = self._any_ira_for_bundle('css')
ltr_date0 = ltr_ira0.create_date
ltr_bundle1 = self._get_asset(self.cssbundle_xmlid)
ltr_bundle1.css()
self.assertEquals(len(self._any_ira_for_bundle('css')), 1)
ltr_version1 = ltr_bundle1.version
ltr_ira1 = self._any_ira_for_bundle('css')
ltr_date1 = ltr_ira1.create_date
self.assertEquals(ltr_version0, ltr_version1)
self.assertEquals(ltr_date0, ltr_date1)
# Assets access for ar_SY language
rtl_bundle0 = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'lang': 'ar_SY'}))
rtl_bundle0.css()
self.assertEquals(len(self._any_ira_for_bundle('css', lang='ar_SY')), 1)
rtl_version0 = rtl_bundle0.version
rtl_ira0 = self._any_ira_for_bundle('css', lang='ar_SY')
rtl_date0 = rtl_ira0.create_date
rtl_bundle1 = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'lang': 'ar_SY'}))
rtl_bundle1.css()
self.assertEquals(len(self._any_ira_for_bundle('css', lang='ar_SY')), 1)
rtl_version1 = rtl_bundle1.version
rtl_ira1 = self._any_ira_for_bundle('css', lang='ar_SY')
rtl_date1 = rtl_ira1.create_date
self.assertEquals(rtl_version0, rtl_version1)
self.assertEquals(rtl_date0, rtl_date1)
# Checks rtl and ltr bundles are different
self.assertNotEquals(ltr_ira1.id, rtl_ira1.id)
# Check two bundles are available, one for ltr and one for rtl
css_bundles = self.env['ir.attachment'].search([
('url', '=like', '/web/content/%-%/{0}%.{1}'.format(self.cssbundle_xmlid, 'css'))
])
self.assertEquals(len(css_bundles), 2)
def test_17_css_bundle_date_invalidation(self):
""" Checks that both css bundles are invalidated when one of its assets' modification date is changed
"""
# Assets access for en_US language
ltr_bundle0 = self._get_asset(self.cssbundle_xmlid)
ltr_bundle0.css()
ltr_last_modified0 = ltr_bundle0.last_modified
ltr_version0 = ltr_bundle0.version
# Assets access for ar_SY language
rtl_bundle0 = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'lang': 'ar_SY'}))
rtl_bundle0.css()
rtl_last_modified0 = rtl_bundle0.last_modified
rtl_version0 = rtl_bundle0.version
# Touch test_cssfile1.css
# Note: No lang specific context given while calling _get_asset so it will load assets for en_US
path = get_resource_path('test_assetsbundle', 'static', 'src', 'css', 'test_cssfile1.css')
ltr_bundle1 = self._get_asset(self.cssbundle_xmlid)
_touch(path, ltr_bundle1)
ltr_bundle1.css()
ltr_last_modified1 = ltr_bundle1.last_modified
ltr_version1 = ltr_bundle1.version
ltr_ira1 = self._any_ira_for_bundle('css')
self.assertNotEquals(ltr_last_modified0, ltr_last_modified1)
self.assertNotEquals(ltr_version0, ltr_version1)
rtl_bundle1 = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'lang': 'ar_SY'}))
rtl_bundle1.css()
rtl_last_modified1 = rtl_bundle1.last_modified
rtl_version1 = rtl_bundle1.version
rtl_ira1 = self._any_ira_for_bundle('css', lang='ar_SY')
self.assertNotEquals(rtl_last_modified0, rtl_last_modified1)
self.assertNotEquals(rtl_version0, rtl_version1)
# Checks rtl and ltr bundles are different
self.assertNotEquals(ltr_ira1.id, rtl_ira1.id)
# check if the previous attachment is correctly cleaned
css_bundles = self.env['ir.attachment'].search([
('url', '=like', '/web/content/%-%/{0}%.{1}'.format(self.cssbundle_xmlid, 'css'))
])
self.assertEquals(len(css_bundles), 2)
def test_18_css_bundle_content_invalidation(self):
""" Checks that a bundle is invalidated when its content is modified by adding a file to
source.
"""
# Assets for en_US
ltr_bundle0 = self._get_asset(self.cssbundle_xmlid)
ltr_bundle0.css()
ltr_files0 = ltr_bundle0.files
ltr_version0 = ltr_bundle0.version
rtl_bundle0 = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'lang': 'ar_SY'}))
rtl_bundle0.css()
rtl_files0 = rtl_bundle0.files
rtl_version0 = rtl_bundle0.version
css_bundles = self.env['ir.attachment'].search([
('url', '=like', '/web/content/%-%/{0}%.{1}'.format(self.cssbundle_xmlid, 'css'))
])
self.assertEquals(len(css_bundles), 2)
view_arch = """
<data>
<xpath expr="." position="inside">
<script type="text/css" src="/test_assetsbundle/static/src/css/test_cssfile3.css"/>
</xpath>
</data>
"""
bundle = self.browse_ref(self.cssbundle_xmlid)
view = self.env['ir.ui.view'].create({
'name': 'test bundle inheritance',
'type': 'qweb',
'arch': view_arch,
'inherit_id': bundle.id,
})
ltr_bundle1 = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'check_view_ids': view.ids}))
ltr_bundle1.css()
ltr_files1 = ltr_bundle1.files
ltr_version1 = ltr_bundle1.version
ltr_ira1 = self._any_ira_for_bundle('css')
self.assertNotEquals(ltr_files0, ltr_files1)
self.assertNotEquals(ltr_version0, ltr_version1)
rtl_bundle1 = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'check_view_ids': view.ids, 'lang': 'ar_SY'}))
rtl_bundle1.css()
rtl_files1 = rtl_bundle1.files
rtl_version1 = rtl_bundle1.version
rtl_ira1 = self._any_ira_for_bundle('css', lang='ar_SY')
self.assertNotEquals(rtl_files0, rtl_files1)
self.assertNotEquals(rtl_version0, rtl_version1)
# Checks rtl and ltr bundles are different
self.assertNotEquals(ltr_ira1.id, rtl_ira1.id)
# check if the previous attachment are correctly cleaned
css_bundles = self.env['ir.attachment'].search([
('url', '=like', '/web/content/%-%/{0}%.{1}'.format(self.cssbundle_xmlid, 'css'))
])
self.assertEquals(len(css_bundles), 2)
def test_19_css_in_debug_assets(self):
""" Checks that a bundle rendered in debug mode(assets) with right to left language direction stores css files in assets bundle.
"""
debug_bundle = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'lang': 'ar_SY'}))
content = debug_bundle.to_html(debug='assets')
# css file should be available in assets bundle as user's lang direction is rtl
self.assertIn('/test_assetsbundle/static/src/css/test_cssfile1/rtl/{0}.css'.format(self.cssbundle_xmlid), content)
# there should be assets(css) created in debug mode as user's lang direction is rtl
css_bundle = self.env['ir.attachment'].search([
('url', '=', '/test_assetsbundle/static/src/css/test_cssfile1/rtl/{0}.css'.format(self.cssbundle_xmlid))
])
self.assertEquals(len(css_bundle), 1)
def test_20_exteral_lib_assets(self):
html = self.env['ir.ui.view'].render_template('test_assetsbundle.template2')
attachments = self.env['ir.attachment'].search([('url', '=like', '/web/content/%-%/test_assetsbundle.bundle4.%')])
self.assertEquals(len(attachments), 2)
@@ -331,7 +533,7 @@ class TestJavascriptAssetsBundle(TransactionCase):
</body>
</html>""" % {"js": attachments[0].url, "css": attachments[1].url}).encode('utf8'))
def test_16_exteral_lib_assets_debug_mode(self):
def test_21_exteral_lib_assets_debug_mode(self):
html = self.env['ir.ui.view'].render_template('test_assetsbundle.template2', {"debug": "assets"})
attachments = self.env['ir.attachment'].search([('url', '=like', '/web/content/%-%/test_assetsbundle.bundle4.%')])
self.assertEquals(len(attachments), 0)
@@ -354,7 +556,6 @@ class TestJavascriptAssetsBundle(TransactionCase):
</html>""").encode('utf8'))
class TestAssetsBundleInBrowser(HttpCase):
def test_01_js_interpretation(self):
""" Checks that the javascript of a bundle is correctly interpreted.