17.0 vanilla

This commit is contained in:
Ernad Husremovic 2025-10-03 18:05:14 +02:00
parent 2e65bf056a
commit df627a6bba
328 changed files with 578149 additions and 759311 deletions

View file

@ -16,8 +16,8 @@ __path__ = [
] ]
import sys import sys
MIN_PY_VERSION = (3, 7) MIN_PY_VERSION = (3, 10)
MAX_PY_VERSION = (3, 12) MAX_PY_VERSION = (3, 13)
assert sys.version_info > MIN_PY_VERSION, f"Outdated python version detected, Odoo requires Python >= {'.'.join(map(str, MIN_PY_VERSION))} to run." assert sys.version_info > MIN_PY_VERSION, f"Outdated python version detected, Odoo requires Python >= {'.'.join(map(str, MIN_PY_VERSION))} to run."
#---------------------------------------------------------- #----------------------------------------------------------
@ -68,26 +68,6 @@ import time
if hasattr(time, 'tzset'): if hasattr(time, 'tzset'):
time.tzset() time.tzset()
#----------------------------------------------------------
# PyPDF2 hack
# ensure that zlib does not throw error -5 when decompressing
# because some pdf won't fit into allocated memory
# https://docs.python.org/3/library/zlib.html#zlib.decompressobj
# ----------------------------------------------------------
import PyPDF2
try:
import zlib
def _decompress(data):
zobj = zlib.decompressobj()
return zobj.decompress(data)
import PyPDF2.filters # needed after PyPDF2 2.0.0 and before 2.11.0
PyPDF2.filters.decompress = _decompress
except ImportError:
pass # no fix required
# --------------------------------------------------------- # ---------------------------------------------------------
# some charset are known by Python under a different name # some charset are known by Python under a different name
# --------------------------------------------------------- # ---------------------------------------------------------

View file

@ -8,9 +8,6 @@ from . import report
from . import wizard from . import wizard
def post_init(cr, registry): def post_init(env):
"""Rewrite ICP's to force groups""" """Rewrite ICP's to force groups"""
from odoo import api, SUPERUSER_ID
env = api.Environment(cr, SUPERUSER_ID, {})
env['ir.config_parameter'].init(force=True) env['ir.config_parameter'].init(force=True)

View file

@ -21,7 +21,6 @@ The kernel of Odoo, needed for all installation.
'data/report_paperformat_data.xml', 'data/report_paperformat_data.xml',
'data/res_country_data.xml', 'data/res_country_data.xml',
'data/ir_demo_data.xml', 'data/ir_demo_data.xml',
'data/ir_config_parameter_data.xml',
'security/base_groups.xml', 'security/base_groups.xml',
'security/base_security.xml', 'security/base_security.xml',
'views/base_menus.xml', 'views/base_menus.xml',
@ -59,7 +58,6 @@ The kernel of Odoo, needed for all installation.
'wizard/base_module_uninstall_views.xml', 'wizard/base_module_uninstall_views.xml',
'wizard/base_export_language_views.xml', 'wizard/base_export_language_views.xml',
'wizard/base_partner_merge_views.xml', 'wizard/base_partner_merge_views.xml',
'data/ir_actions_data.xml',
'data/ir_demo_failure_data.xml', 'data/ir_demo_failure_data.xml',
'views/ir_profile_views.xml', 'views/ir_profile_views.xml',
'views/res_company_views.xml', 'views/res_company_views.xml',
@ -69,16 +67,16 @@ The kernel of Odoo, needed for all installation.
'views/res_country_views.xml', 'views/res_country_views.xml',
'views/res_currency_views.xml', 'views/res_currency_views.xml',
'views/res_users_views.xml', 'views/res_users_views.xml',
'views/res_users_identitycheck_views.xml',
'views/ir_property_views.xml', 'views/ir_property_views.xml',
'views/res_config_settings_views.xml', 'views/res_config_settings_views.xml',
'views/report_paperformat_views.xml', 'views/report_paperformat_views.xml',
'views/onboarding_views.xml',
'security/ir.model.access.csv', 'security/ir.model.access.csv',
], ],
'demo': [ 'demo': [
'data/res_company_demo.xml',
'data/res_users_demo.xml', 'data/res_users_demo.xml',
'data/res_partner_bank_demo.xml', 'data/res_partner_bank_demo.xml',
'data/res_currency_demo.xml',
'data/res_currency_rate_demo.xml', 'data/res_currency_rate_demo.xml',
'data/res_bank_demo.xml', 'data/res_bank_demo.xml',
'data/res_partner_demo.xml', 'data/res_partner_demo.xml',

View file

@ -119,8 +119,8 @@ CREATE TABLE res_partner (
--------------------------------- ---------------------------------
-- Default data -- Default data
--------------------------------- ---------------------------------
insert into res_currency (id, name, symbol) VALUES (1, 'EUR', ''); insert into res_currency (id, name, symbol) VALUES (1, 'USD', '$');
insert into ir_model_data (name, module, model, noupdate, res_id) VALUES ('EUR', 'base', 'res.currency', true, 1); insert into ir_model_data (name, module, model, noupdate, res_id) VALUES ('USD', 'base', 'res.currency', true, 1);
select setval('res_currency_id_seq', 1); select setval('res_currency_id_seq', 1);
insert into res_company (id, name, partner_id, currency_id, create_date) VALUES (1, 'My Company', 1, 1, now() at time zone 'UTC'); insert into res_company (id, name, partner_id, currency_id, create_date) VALUES (1, 'My Company', 1, 1, now() at time zone 'UTC');

View file

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<record id="action_server_module_immediate_install" model="ir.actions.server">
<field name="name">Activate Modules</field>
<field name="type">ir.actions.server</field>
<field name="model_id" ref="model_ir_module_module" />
<field name="binding_model_id" ref="model_ir_module_module" />
<field name="state">code</field>
<field name="code">records.button_immediate_install()</field>
</record>
</data>
</odoo>

View file

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Catchall Email Alias -->
<record id="icp_mail_catchall_alias" model="ir.config_parameter">
<field name="key">mail.catchall.alias</field>
<field name="value">catchall</field>
</record>
<!-- Bounce Email Alias -->
<record id="icp_mail_bounce_alias" model="ir.config_parameter">
<field name="key">mail.bounce.alias</field>
<field name="value">bounce</field>
</record>
<!-- Notifications -->
<record id="icp_mail_default_from" model="ir.config_parameter">
<field name="key">mail.default.from</field>
<field name="value">notifications</field>
</record>
</data>
</odoo>

View file

@ -8,5 +8,16 @@
<field name='interval_number'>1</field> <field name='interval_number'>1</field>
<field name='interval_type'>days</field> <field name='interval_type'>days</field>
<field name="numbercall">-1</field> <field name="numbercall">-1</field>
<field name="priority">3</field>
</record>
<record id="ir_cron_res_users_deletion" model="ir.cron">
<field name="name">Base: Portal Users Deletion</field>
<field name="model_id" ref="base.model_res_users_deletion"/>
<field name="state">code</field>
<field name="code">model._gc_portal_users()</field>
<field name='interval_number'>1</field>
<field name='interval_type'>days</field>
<field name="numbercall">-1</field>
</record> </record>
</odoo> </odoo>

View file

@ -28,7 +28,7 @@
</div> </div>
<footer> <footer>
<button special="cancel" data-hotkey="z" string="Oops, no!" class="btn-primary"/> <button special="cancel" data-hotkey="x" string="Oops, no!" class="btn-primary"/>
<button name="install_demo" string="Yes, I understand the risks" type="object" class="btn-secondary" data-hotkey="q"/> <button name="install_demo" string="Yes, I understand the risks" type="object" class="btn-secondary" data-hotkey="q"/>
</footer> </footer>
</form> </form>

View file

@ -101,6 +101,11 @@
<field name="sequence">14</field> <field name="sequence">14</field>
</record> </record>
<record model="ir.module.category" id="module_category_services_appointment">
<field name="name">Appointment</field>
<field name="parent_id" ref="module_category_services"/>
</record>
<record model="ir.module.category" id="module_category_services_field_service"> <record model="ir.module.category" id="module_category_services_field_service">
<field name="name">Field Service</field> <field name="name">Field Service</field>
<field name="parent_id" ref="module_category_services"/> <field name="parent_id" ref="module_category_services"/>

View file

@ -142,6 +142,20 @@
<field name="website">https://www.odoo.com/app/quality?utm_source=db&amp;utm_medium=module</field> <field name="website">https://www.odoo.com/app/quality?utm_source=db&amp;utm_medium=module</field>
</record> </record>
<record model="ir.module.module" id="base.module_sale_amazon">
<field name="name">sale_amazon</field>
<field name="shortdesc">Amazon Connector</field>
<field name="sequence">320</field>
<field name="category_id" ref="base.module_category_sales_sales"/>
<field name="application" eval="True"/>
<field name="summary">Import Amazon orders and sync deliveries</field>
<field name="license">OEEL-1</field>
<field name="author">Odoo S.A.</field>
<field name="to_buy" eval="True"/>
<field name="icon">/base/static/img/icons/sale_amazon.png</field>
<field name="website">https://www.odoo.com/app/amazon-connector?utm_source=db&amp;utm_medium=module</field>
</record>
<record model="ir.module.module" id="base.module_sale_ebay"> <record model="ir.module.module" id="base.module_sale_ebay">
<field name="name">sale_ebay</field> <field name="name">sale_ebay</field>
<field name="shortdesc">eBay Connector</field> <field name="shortdesc">eBay Connector</field>

View file

@ -22,3 +22,8 @@ INSERT INTO ir_config_parameter (key, value)
VALUES ('database.is_neutralized', true) VALUES ('database.is_neutralized', true)
ON CONFLICT (key) DO ON CONFLICT (key) DO
UPDATE SET value = true; UPDATE SET value = true;
-- deactivate webhooks
UPDATE ir_act_server
SET webhook_url = 'neutralization - disable webhook'
WHERE state = 'webhook';

View file

@ -467,22 +467,22 @@ state_es_va,es,"Valladolid","VA"
state_es_bi,es,"Bizkaia (Vizcaya)","BI" state_es_bi,es,"Bizkaia (Vizcaya)","BI"
state_es_za,es,"Zamora","ZA" state_es_za,es,"Zamora","ZA"
state_es_z,es,"Zaragoza","Z" state_es_z,es,"Zaragoza","Z"
state_my_jhr,my,"Johor","JHR" state_my_jhr,my,"Johor","MY-01"
state_my_kdh,my,"Kedah","KDH" state_my_kdh,my,"Kedah","MY-02"
state_my_ktn,my,"Kelantan","KTN" state_my_ktn,my,"Kelantan","MY-03"
state_my_kul,my,"Kuala Lumpur","KUL" state_my_kul,my,"Kuala Lumpur","MY-14"
state_my_lbn,my,"Labuan","LBN" state_my_lbn,my,"Labuan","MY-15"
state_my_mlk,my,"Melaka","MLK" state_my_mlk,my,"Melaka","MY-04"
state_my_nsn,my,"Negeri Sembilan","NSN" state_my_nsn,my,"Negeri Sembilan","MY-05"
state_my_phg,my,"Pahang","PHG" state_my_phg,my,"Pahang","MY-06"
state_my_prk,my,"Perak","PRK" state_my_prk,my,"Perak","MY-08"
state_my_pls,my,"Perlis","PLS" state_my_pls,my,"Perlis","MY-09"
state_my_png,my,"Pulau Pinang","PNG" state_my_png,my,"Pulau Pinang","MY-07"
state_my_pjy,my,"Putrajaya","PJY" state_my_pjy,my,"Putrajaya","MY-16"
state_my_sbh,my,"Sabah","SBH" state_my_sbh,my,"Sabah","MY-12"
state_my_swk,my,"Sarawak","SWK" state_my_swk,my,"Sarawak","MY-13"
state_my_sgr,my,"Selangor","SGR" state_my_sgr,my,"Selangor","MY-10"
state_my_trg,my,"Terengganu","TRG" state_my_trg,my,"Terengganu","MY-11"
state_mx_ags,mx,"Aguascalientes","AGU" state_mx_ags,mx,"Aguascalientes","AGU"
state_mx_bc,mx,"Baja California","BCN" state_mx_bc,mx,"Baja California","BCN"
state_mx_bcs,mx,"Baja California Sur","BCS" state_mx_bcs,mx,"Baja California Sur","BCS"
@ -1667,3 +1667,84 @@ state_sa_90,sa,"Yanbu commercial city","YNB"
state_sa_91,sa,"Yanbu Industrial City","YBI" state_sa_91,sa,"Yanbu Industrial City","YBI"
state_sa_92,sa,"Zilfi","ZUL" state_sa_92,sa,"Zilfi","ZUL"
state_sa_93,sa,"Zulayfayn","ZUY" state_sa_93,sa,"Zulayfayn","ZUY"
state_uy_01,uy,"Artigas","AR"
state_uy_02,uy,"Canelones","CA"
state_uy_03,uy,"Cerro Largo","CL"
state_uy_04,uy,"Colonia","CO"
state_uy_05,uy,"Durazno","DU"
state_uy_06,uy,"Flores","FS"
state_uy_07,uy,"Florida","FD"
state_uy_08,uy,"Lavalleja","LA"
state_uy_09,uy,"Maldonado","MA"
state_uy_10,uy,"Montevideo","MO"
state_uy_11,uy,"Paysandú","PA"
state_uy_12,uy,"Río Negro","RN"
state_uy_13,uy,"Rivera","RV"
state_uy_14,uy,"Rocha","RO"
state_uy_15,uy,"Salto","SA"
state_uy_16,uy,"San José","SJ"
state_uy_17,uy,"Soriano","SO"
state_uy_18,uy,"Tacuarembó","TA"
state_uy_19,uy,"Treinta y Tres","TT"
state_hk_hk,base.hk,Hong Kong Island,HK
state_hk_kln,base.hk,Kowloon,KLN
state_hk_nt,base.hk,New Territories,NT
state_ke_01,ke,"Baringo",KE-01
state_ke_02,ke,"Bomet",KE-02
state_ke_03,ke,"Bungoma",KE-03
state_ke_04,ke,"Busia",KE-04
state_ke_05,ke,"Elgeyo/Marakwet",KE-05
state_ke_06,ke,"Embu",KE-06
state_ke_07,ke,"Garissa",KE-07
state_ke_08,ke,"Homa Bay",KE-08
state_ke_09,ke,"Isiolo",KE-09
state_ke_10,ke,"Kajiado",KE-10
state_ke_11,ke,"Kakamega",KE-11
state_ke_12,ke,"Kericho",KE-12
state_ke_13,ke,"Kiambu",KE-13
state_ke_14,ke,"Kilifi",KE-14
state_ke_15,ke,"Kirinyaga",KE-15
state_ke_16,ke,"Kisii",KE-16
state_ke_17,ke,"Kisumu",KE-17
state_ke_18,ke,"Kitui",KE-18
state_ke_19,ke,"Kwale",KE-19
state_ke_20,ke,"Laikipia",KE-20
state_ke_21,ke,"Lamu",KE-21
state_ke_22,ke,"Machakos",KE-22
state_ke_23,ke,"Makueni",KE-23
state_ke_24,ke,"Mandera",KE-24
state_ke_25,ke,"Marsabit",KE-25
state_ke_26,ke,"Meru",KE-26
state_ke_27,ke,"Migori",KE-27
state_ke_28,ke,"Mombasa",KE-28
state_ke_29,ke,"Murang'a",KE-29
state_ke_30,ke,"Nairobi City",KE-30
state_ke_31,ke,"Nakuru",KE-31
state_ke_32,ke,"Nandi",KE-32
state_ke_33,ke,"Narok",KE-33
state_ke_34,ke,"Nyamira",KE-34
state_ke_35,ke,"Nyandarua",KE-35
state_ke_36,ke,"Nyeri",KE-36
state_ke_37,ke,"Samburu",KE-37
state_ke_38,ke,"Siaya",KE-38
state_ke_39,ke,"Taita/Taveta",KE-39
state_ke_40,ke,"Tana River",KE-40
state_ke_41,ke,"Tharaka-Nithi",KE-41
state_ke_42,ke,"Trans Nzoia",KE-42
state_ke_43,ke,"Turkana",KE-43
state_ke_44,ke,"Uasin Gishu",KE-44
state_ke_45,ke,"Vihiga",KE-45
state_ke_46,ke,"Wajir",KE-46
state_ke_47,ke,"West Pokot",KE-47
state_jo_aj,jo,"Ajloun",JO-AJ
state_jo_am,jo,"Amman",JO-AM
state_jo_aq,jo,"Aqaba",JO-AQ
state_jo_at,jo,"Tafileh",JO-AT
state_jo_az,jo,"Zarqa",JO-AZ
state_jo_ba,jo,"Balqa",JO-BA
state_jo_ir,jo,"Irbid",JO-IR
state_jo_ja,jo,"Jerash",JO-JA
state_jo_ka,jo,"Karak",JO-KA
state_jo_ma,jo,"Mafraq",JO-MA
state_jo_md,jo,"Madaba",JO-MD
state_jo_mn,jo,"Maan",JO-MN

1 id country_id:id name code
467 state_es_bi es Bizkaia (Vizcaya) BI
468 state_es_za es Zamora ZA
469 state_es_z es Zaragoza Z
470 state_my_jhr my Johor JHR MY-01
471 state_my_kdh my Kedah KDH MY-02
472 state_my_ktn my Kelantan KTN MY-03
473 state_my_kul my Kuala Lumpur KUL MY-14
474 state_my_lbn my Labuan LBN MY-15
475 state_my_mlk my Melaka MLK MY-04
476 state_my_nsn my Negeri Sembilan NSN MY-05
477 state_my_phg my Pahang PHG MY-06
478 state_my_prk my Perak PRK MY-08
479 state_my_pls my Perlis PLS MY-09
480 state_my_png my Pulau Pinang PNG MY-07
481 state_my_pjy my Putrajaya PJY MY-16
482 state_my_sbh my Sabah SBH MY-12
483 state_my_swk my Sarawak SWK MY-13
484 state_my_sgr my Selangor SGR MY-10
485 state_my_trg my Terengganu TRG MY-11
486 state_mx_ags mx Aguascalientes AGU
487 state_mx_bc mx Baja California BCN
488 state_mx_bcs mx Baja California Sur BCS
1667 state_sa_91 sa Yanbu Industrial City YBI
1668 state_sa_92 sa Zilfi ZUL
1669 state_sa_93 sa Zulayfayn ZUY
1670 state_uy_01 uy Artigas AR
1671 state_uy_02 uy Canelones CA
1672 state_uy_03 uy Cerro Largo CL
1673 state_uy_04 uy Colonia CO
1674 state_uy_05 uy Durazno DU
1675 state_uy_06 uy Flores FS
1676 state_uy_07 uy Florida FD
1677 state_uy_08 uy Lavalleja LA
1678 state_uy_09 uy Maldonado MA
1679 state_uy_10 uy Montevideo MO
1680 state_uy_11 uy Paysandú PA
1681 state_uy_12 uy Río Negro RN
1682 state_uy_13 uy Rivera RV
1683 state_uy_14 uy Rocha RO
1684 state_uy_15 uy Salto SA
1685 state_uy_16 uy San José SJ
1686 state_uy_17 uy Soriano SO
1687 state_uy_18 uy Tacuarembó TA
1688 state_uy_19 uy Treinta y Tres TT
1689 state_hk_hk base.hk Hong Kong Island HK
1690 state_hk_kln base.hk Kowloon KLN
1691 state_hk_nt base.hk New Territories NT
1692 state_ke_01 ke Baringo KE-01
1693 state_ke_02 ke Bomet KE-02
1694 state_ke_03 ke Bungoma KE-03
1695 state_ke_04 ke Busia KE-04
1696 state_ke_05 ke Elgeyo/Marakwet KE-05
1697 state_ke_06 ke Embu KE-06
1698 state_ke_07 ke Garissa KE-07
1699 state_ke_08 ke Homa Bay KE-08
1700 state_ke_09 ke Isiolo KE-09
1701 state_ke_10 ke Kajiado KE-10
1702 state_ke_11 ke Kakamega KE-11
1703 state_ke_12 ke Kericho KE-12
1704 state_ke_13 ke Kiambu KE-13
1705 state_ke_14 ke Kilifi KE-14
1706 state_ke_15 ke Kirinyaga KE-15
1707 state_ke_16 ke Kisii KE-16
1708 state_ke_17 ke Kisumu KE-17
1709 state_ke_18 ke Kitui KE-18
1710 state_ke_19 ke Kwale KE-19
1711 state_ke_20 ke Laikipia KE-20
1712 state_ke_21 ke Lamu KE-21
1713 state_ke_22 ke Machakos KE-22
1714 state_ke_23 ke Makueni KE-23
1715 state_ke_24 ke Mandera KE-24
1716 state_ke_25 ke Marsabit KE-25
1717 state_ke_26 ke Meru KE-26
1718 state_ke_27 ke Migori KE-27
1719 state_ke_28 ke Mombasa KE-28
1720 state_ke_29 ke Murang'a KE-29
1721 state_ke_30 ke Nairobi City KE-30
1722 state_ke_31 ke Nakuru KE-31
1723 state_ke_32 ke Nandi KE-32
1724 state_ke_33 ke Narok KE-33
1725 state_ke_34 ke Nyamira KE-34
1726 state_ke_35 ke Nyandarua KE-35
1727 state_ke_36 ke Nyeri KE-36
1728 state_ke_37 ke Samburu KE-37
1729 state_ke_38 ke Siaya KE-38
1730 state_ke_39 ke Taita/Taveta KE-39
1731 state_ke_40 ke Tana River KE-40
1732 state_ke_41 ke Tharaka-Nithi KE-41
1733 state_ke_42 ke Trans Nzoia KE-42
1734 state_ke_43 ke Turkana KE-43
1735 state_ke_44 ke Uasin Gishu KE-44
1736 state_ke_45 ke Vihiga KE-45
1737 state_ke_46 ke Wajir KE-46
1738 state_ke_47 ke West Pokot KE-47
1739 state_jo_aj jo Ajloun JO-AJ
1740 state_jo_am jo Amman JO-AM
1741 state_jo_aq jo Aqaba JO-AQ
1742 state_jo_at jo Tafileh JO-AT
1743 state_jo_az jo Zarqa JO-AZ
1744 state_jo_ba jo Balqa JO-BA
1745 state_jo_ir jo Irbid JO-IR
1746 state_jo_ja jo Jerash JO-JA
1747 state_jo_ka jo Karak JO-KA
1748 state_jo_ma jo Mafraq JO-MA
1749 state_jo_md jo Madaba JO-MD
1750 state_jo_mn jo Maan JO-MN

View file

@ -1,8 +1,8 @@
"id","name","code","iso_code","direction","grouping","decimal_point","thousands_sep","date_format","time_format","week_start" "id","name","code","iso_code","direction","grouping","decimal_point","thousands_sep","date_format","time_format","week_start"
"base.lang_en","English (US)","en_US","en","Left-to-Right","[3,0]",".",",","%m/%d/%Y","%H:%M:%S","7" "base.lang_en","English (US)","en_US","en","Left-to-Right","[3,0]",".",",","%m/%d/%Y","%H:%M:%S","7"
"base.lang_am_ET","Amharic / አምሃርኛ","am_ET","am_ET","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%I:%M:%S","7" "base.lang_am_ET","Amharic / አምሃርኛ","am_ET","am_ET","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%I:%M:%S","7"
"base.lang_ar","Arabic / الْعَرَبيّة","ar_001","ar","Right-to-Left","[3,0]",".",",","%d %b, %Y","%I:%M:%S","6" "base.lang_ar","Arabic / الْعَرَبيّة","ar_001","ar","Right-to-Left","[3,0]",".",",","%d %b, %Y","%I:%M:%S %p","6"
"base.lang_ar_SY","Arabic (Syria) / الْعَرَبيّة","ar_SY","ar_SY","Right-to-Left","[3,0]",".",",","%d %b, %Y","%I:%M:%S","6" "base.lang_ar_SY","Arabic (Syria) / الْعَرَبيّة","ar_SY","ar_SY","Right-to-Left","[3,0]",".",",","%d %b, %Y","%I:%M:%S %p","6"
"base.lang_az","Azerbaijani / Azərbaycanca","az_AZ","az","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H:%M:%S","1" "base.lang_az","Azerbaijani / Azərbaycanca","az_AZ","az","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H:%M:%S","1"
"base.lang_eu_ES","Basque / Euskara","eu_ES","eu_ES","Left-to-Right","[]",",",,"%a, %Y.eko %bren %da","%H:%M:%S","1" "base.lang_eu_ES","Basque / Euskara","eu_ES","eu_ES","Left-to-Right","[]",",",,"%a, %Y.eko %bren %da","%H:%M:%S","1"
"base.lang_bn_IN","Bengali / বাংলা","bn_IN","bn_IN","Left-to-Right","[]",",",,"%A %d %b %Y","%I:%M:%S","1" "base.lang_bn_IN","Bengali / বাংলা","bn_IN","bn_IN","Left-to-Right","[]",",",,"%A %d %b %Y","%I:%M:%S","1"
@ -21,6 +21,7 @@
"base.lang_en_CA","English (CA)","en_CA","en_CA","Left-to-Right","[3,0]",".",",","%Y-%m-%d","%H:%M:%S","7" "base.lang_en_CA","English (CA)","en_CA","en_CA","Left-to-Right","[3,0]",".",",","%Y-%m-%d","%H:%M:%S","7"
"base.lang_en_GB","English (UK)","en_GB","en_GB","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","1" "base.lang_en_GB","English (UK)","en_GB","en_GB","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","1"
"base.lang_en_IN","English (IN)","en_IN","en_IN","Left-to-Right","[3,2,0]",".",",","%d/%m/%Y","%H:%M:%S","7" "base.lang_en_IN","English (IN)","en_IN","en_IN","Left-to-Right","[3,2,0]",".",",","%d/%m/%Y","%H:%M:%S","7"
"base.lang_en_NZ","English (NZ)","en_NZ","en_NZ","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","7"
"base.lang_et_EE","Estonian / Eesti keel","et_EE","et","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H:%M:%S","1" "base.lang_et_EE","Estonian / Eesti keel","et_EE","et","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H:%M:%S","1"
"base.lang_fi","Finnish / Suomi","fi_FI","fi","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H.%M.%S","1" "base.lang_fi","Finnish / Suomi","fi_FI","fi","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H.%M.%S","1"
"base.lang_fr_BE","French (BE) / Français (BE)","fr_BE","fr_BE","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","1" "base.lang_fr_BE","French (BE) / Français (BE)","fr_BE","fr_BE","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","1"
@ -43,7 +44,7 @@
"base.lang_km","Khmer / ភាសាខ្មែរ","km_KH","km","Left-to-Right","[3,0]",".",",","%d %B %Y","%H:%M:%S","7" "base.lang_km","Khmer / ភាសាខ្មែរ","km_KH","km","Left-to-Right","[3,0]",".",",","%d %B %Y","%H:%M:%S","7"
"base.lang_ko_KP","Korean (KP) / 한국어 (KP)","ko_KP","ko_KP","Left-to-Right","[3,0]",".",",","%m/%d/%Y","%I:%M:%S %p","1" "base.lang_ko_KP","Korean (KP) / 한국어 (KP)","ko_KP","ko_KP","Left-to-Right","[3,0]",".",",","%m/%d/%Y","%I:%M:%S %p","1"
"base.lang_ko_KR","Korean (KR) / 한국어 (KR)","ko_KR","ko_KR","Left-to-Right","[3,0]",".",",","%Y년 %m월 %d일","%H시 %M분 %S초","7" "base.lang_ko_KR","Korean (KR) / 한국어 (KR)","ko_KR","ko_KR","Left-to-Right","[3,0]",".",",","%Y년 %m월 %d일","%H시 %M분 %S초","7"
"base.lang_lo_LA","Lao / ພາສາລາວ","lo_LA","lo","Left-to-Right","[3,0]",".",",","%d/%m/y","%H:%M:%S","7" "base.lang_lo_LA","Lao / ພາສາລາວ","lo_LA","lo","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","7"
"base.lang_lv","Latvian / latviešu valoda","lv_LV","lv","Left-to-Right","[3,0]",","," ","%Y.%m.%d.","%H:%M:%S","1" "base.lang_lv","Latvian / latviešu valoda","lv_LV","lv","Left-to-Right","[3,0]",","," ","%Y.%m.%d.","%H:%M:%S","1"
"base.lang_lt","Lithuanian / Lietuvių kalba","lt_LT","lt","Left-to-Right","[3,0]",",",".","%Y-%m-%d","%H:%M:%S","1" "base.lang_lt","Lithuanian / Lietuvių kalba","lt_LT","lt","Left-to-Right","[3,0]",",",".","%Y-%m-%d","%H:%M:%S","1"
"base.lang_lb","Luxembourgish","lb_LU","lb","Left-to-Right","[3,0]",","," ","%d/%m/%Y","%H:%M:%S","1" "base.lang_lb","Luxembourgish","lb_LU","lb","Left-to-Right","[3,0]",","," ","%d/%m/%Y","%H:%M:%S","1"
@ -64,6 +65,7 @@ base.lang_my,"Burmese / ဗမာစာ",my_MM,my,"Left-to-Right","[3,3]",".",",
"base.lang_sr@latin","Serbian (Latin) / srpski","sr@latin","sr@latin","Left-to-Right","[]",".",",","%m/%d/%Y","%I:%M:%S %p","7" "base.lang_sr@latin","Serbian (Latin) / srpski","sr@latin","sr@latin","Left-to-Right","[]",".",",","%m/%d/%Y","%I:%M:%S %p","7"
"base.lang_sk","Slovak / Slovenský jazyk","sk_SK","sk","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H:%M:%S","1" "base.lang_sk","Slovak / Slovenský jazyk","sk_SK","sk","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H:%M:%S","1"
"base.lang_sl_SI","Slovenian / slovenščina","sl_SI","sl","Left-to-Right","[]",","," ","%d. %m. %Y","%H:%M:%S","1" "base.lang_sl_SI","Slovenian / slovenščina","sl_SI","sl","Left-to-Right","[]",","," ","%d. %m. %Y","%H:%M:%S","1"
"base.lang_es_419","Spanish (Latin America) / Español (América Latina)","es_419","es_419","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","1"
"base.lang_es_AR","Spanish (AR) / Español (AR)","es_AR","es_AR","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","7" "base.lang_es_AR","Spanish (AR) / Español (AR)","es_AR","es_AR","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","7"
"base.lang_es_BO","Spanish (BO) / Español (BO)","es_BO","es_BO","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","1" "base.lang_es_BO","Spanish (BO) / Español (BO)","es_BO","es_BO","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","1"
"base.lang_es_CL","Spanish (CL) / Español (CL)","es_CL","es_CL","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","1" "base.lang_es_CL","Spanish (CL) / Español (CL)","es_CL","es_CL","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","1"
@ -79,6 +81,7 @@ base.lang_my,"Burmese / ဗမာစာ",my_MM,my,"Left-to-Right","[3,3]",".",",
"base.lang_es_UY","Spanish (UY) / Español (UY)","es_UY","es_UY","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","1" "base.lang_es_UY","Spanish (UY) / Español (UY)","es_UY","es_UY","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","1"
"base.lang_es_VE","Spanish (VE) / Español (VE)","es_VE","es_VE","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","7" "base.lang_es_VE","Spanish (VE) / Español (VE)","es_VE","es_VE","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","7"
"base.lang_es","Spanish / Español","es_ES","es","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","1" "base.lang_es","Spanish / Español","es_ES","es","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","1"
"base.lang_sw","Swahili / Kiswahili","sw","sw","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","1"
"base.lang_sv_SE","Swedish / Svenska","sv_SE","sv","Left-to-Right","[3,0]",","," ","%Y-%m-%d","%H:%M:%S","1" "base.lang_sv_SE","Swedish / Svenska","sv_SE","sv","Left-to-Right","[3,0]",","," ","%Y-%m-%d","%H:%M:%S","1"
"base.lang_th","Thai / ภาษาไทย","th_TH","th","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","7" "base.lang_th","Thai / ภาษาไทย","th_TH","th","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","7"
"base.lang_tl","Tagalog / Filipino","tl_PH","tl","Left-to-Right","[3,0]",".",",","%m/%d/%y","%H:%M:%S","1" "base.lang_tl","Tagalog / Filipino","tl_PH","tl","Left-to-Right","[3,0]",".",",","%m/%d/%y","%H:%M:%S","1"

1 id name code iso_code direction grouping decimal_point thousands_sep date_format time_format week_start
2 base.lang_en English (US) en_US en Left-to-Right [3,0] . , %m/%d/%Y %H:%M:%S 7
3 base.lang_am_ET Amharic / አምሃርኛ am_ET am_ET Left-to-Right [3,0] . , %d/%m/%Y %I:%M:%S 7
4 base.lang_ar Arabic / الْعَرَبيّة ar_001 ar Right-to-Left [3,0] . , %d %b, %Y %I:%M:%S %I:%M:%S %p 6
5 base.lang_ar_SY Arabic (Syria) / الْعَرَبيّة ar_SY ar_SY Right-to-Left [3,0] . , %d %b, %Y %I:%M:%S %I:%M:%S %p 6
6 base.lang_az Azerbaijani / Azərbaycanca az_AZ az Left-to-Right [3,0] ,   %d.%m.%Y %H:%M:%S 1
7 base.lang_eu_ES Basque / Euskara eu_ES eu_ES Left-to-Right [] , %a, %Y.eko %bren %da %H:%M:%S 1
8 base.lang_bn_IN Bengali / বাংলা bn_IN bn_IN Left-to-Right [] , %A %d %b %Y %I:%M:%S 1
21 base.lang_en_CA English (CA) en_CA en_CA Left-to-Right [3,0] . , %Y-%m-%d %H:%M:%S 7
22 base.lang_en_GB English (UK) en_GB en_GB Left-to-Right [3,0] . , %d/%m/%Y %H:%M:%S 1
23 base.lang_en_IN English (IN) en_IN en_IN Left-to-Right [3,2,0] . , %d/%m/%Y %H:%M:%S 7
24 base.lang_en_NZ English (NZ) en_NZ en_NZ Left-to-Right [3,0] . , %d/%m/%Y %H:%M:%S 7
25 base.lang_et_EE Estonian / Eesti keel et_EE et Left-to-Right [3,0] ,   %d.%m.%Y %H:%M:%S 1
26 base.lang_fi Finnish / Suomi fi_FI fi Left-to-Right [3,0] ,   %d.%m.%Y %H.%M.%S 1
27 base.lang_fr_BE French (BE) / Français (BE) fr_BE fr_BE Left-to-Right [3,0] , . %d/%m/%Y %H:%M:%S 1
44 base.lang_km Khmer / ភាសាខ្មែរ km_KH km Left-to-Right [3,0] . , %d %B %Y %H:%M:%S 7
45 base.lang_ko_KP Korean (KP) / 한국어 (KP) ko_KP ko_KP Left-to-Right [3,0] . , %m/%d/%Y %I:%M:%S %p 1
46 base.lang_ko_KR Korean (KR) / 한국어 (KR) ko_KR ko_KR Left-to-Right [3,0] . , %Y년 %m월 %d일 %H시 %M분 %S초 7
47 base.lang_lo_LA Lao / ພາສາລາວ lo_LA lo Left-to-Right [3,0] . , %d/%m/y %d/%m/%Y %H:%M:%S 7
48 base.lang_lv Latvian / latviešu valoda lv_LV lv Left-to-Right [3,0] ,   %Y.%m.%d. %H:%M:%S 1
49 base.lang_lt Lithuanian / Lietuvių kalba lt_LT lt Left-to-Right [3,0] , . %Y-%m-%d %H:%M:%S 1
50 base.lang_lb Luxembourgish lb_LU lb Left-to-Right [3,0] ,   %d/%m/%Y %H:%M:%S 1
65 base.lang_sr@latin Serbian (Latin) / srpski sr@latin sr@latin Left-to-Right [] . , %m/%d/%Y %I:%M:%S %p 7
66 base.lang_sk Slovak / Slovenský jazyk sk_SK sk Left-to-Right [3,0] ,   %d.%m.%Y %H:%M:%S 1
67 base.lang_sl_SI Slovenian / slovenščina sl_SI sl Left-to-Right [] ,   %d. %m. %Y %H:%M:%S 1
68 base.lang_es_419 Spanish (Latin America) / Español (América Latina) es_419 es_419 Left-to-Right [3,0] , . %d/%m/%Y %H:%M:%S 1
69 base.lang_es_AR Spanish (AR) / Español (AR) es_AR es_AR Left-to-Right [3,0] , . %d/%m/%Y %H:%M:%S 7
70 base.lang_es_BO Spanish (BO) / Español (BO) es_BO es_BO Left-to-Right [3,0] , . %d/%m/%Y %H:%M:%S 1
71 base.lang_es_CL Spanish (CL) / Español (CL) es_CL es_CL Left-to-Right [3,0] , . %d/%m/%Y %H:%M:%S 1
81 base.lang_es_UY Spanish (UY) / Español (UY) es_UY es_UY Left-to-Right [3,0] , . %d/%m/%Y %H:%M:%S 1
82 base.lang_es_VE Spanish (VE) / Español (VE) es_VE es_VE Left-to-Right [3,0] , . %d/%m/%Y %H:%M:%S 7
83 base.lang_es Spanish / Español es_ES es Left-to-Right [3,0] , . %d/%m/%Y %H:%M:%S 1
84 base.lang_sw Swahili / Kiswahili sw sw Left-to-Right [3,0] . , %d/%m/%Y %H:%M:%S 1
85 base.lang_sv_SE Swedish / Svenska sv_SE sv Left-to-Right [3,0] ,   %Y-%m-%d %H:%M:%S 1
86 base.lang_th Thai / ภาษาไทย th_TH th Left-to-Right [3,0] . , %d/%m/%Y %H:%M:%S 7
87 base.lang_tl Tagalog / Filipino tl_PH tl Left-to-Right [3,0] . , %m/%d/%y %H:%M:%S 1

View file

@ -4,8 +4,7 @@
<record id="main_company" model="res.company"> <record id="main_company" model="res.company">
<field name="name">My Company</field> <field name="name">My Company</field>
<field name="partner_id" ref="main_partner"/> <field name="partner_id" ref="main_partner"/>
<field name="currency_id" ref="base.EUR"/> <field name="currency_id" ref="base.USD"/>
<field name="favicon" model="res.company" eval="obj()._get_default_favicon(original=True)"/>
</record> </record>
</data> </data>
</odoo> </odoo>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="main_company" model="res.company">
<field name="name">ProBike Inc</field>
</record>
</data>
</odoo>

View file

@ -246,7 +246,7 @@
<field eval="'%(street)s\n%(street2)s\n%(city)s %(state_code)s %(zip)s\n%(country_name)s'" name="address_format" /> <field eval="'%(street)s\n%(street2)s\n%(city)s %(state_code)s %(zip)s\n%(country_name)s'" name="address_format" />
<field name="currency_id" ref="CAD" /> <field name="currency_id" ref="CAD" />
<field eval="1" name="phone_code" /> <field eval="1" name="phone_code" />
<field name="vat_label">HST</field> <field name="vat_label">GST/HST number</field>
</record> </record>
<record id="cc" model="res.country"> <record id="cc" model="res.country">
<field name="name">Cocos (Keeling) Islands</field> <field name="name">Cocos (Keeling) Islands</field>
@ -412,6 +412,8 @@
<field name="currency_id" ref="USD" /> <field name="currency_id" ref="USD" />
<field eval="593" name="phone_code" /> <field eval="593" name="phone_code" />
<field name="vat_label">RUC</field> <field name="vat_label">RUC</field>
<field name='zip_required'>0</field>
<field name="address_format" eval="'%(street)s\n%(street2)s\n%(city)s\n%(country_name)s'" />
</record> </record>
<record id="ee" model="res.country"> <record id="ee" model="res.country">
<field name="name">Estonia</field> <field name="name">Estonia</field>
@ -441,7 +443,7 @@
<record id="es" model="res.country"> <record id="es" model="res.country">
<field name="name">Spain</field> <field name="name">Spain</field>
<field name="code">es</field> <field name="code">es</field>
<field eval="'%(street)s\n%(street2)s\n%(zip)s %(city)s (%(state_name)s)\n%(country_name)s'" name="address_format"/> <field eval="'%(street)s\n%(street2)s\n%(zip)s %(city)s\n%(state_name)s\n%(country_name)s'" name="address_format"/>
<field name="currency_id" ref="EUR" /> <field name="currency_id" ref="EUR" />
<field eval="34" name="phone_code" /> <field eval="34" name="phone_code" />
<field name="vat_label">VAT</field> <field name="vat_label">VAT</field>
@ -1095,7 +1097,7 @@
<field name="code">nz</field> <field name="code">nz</field>
<field name="currency_id" ref="NZD" /> <field name="currency_id" ref="NZD" />
<field eval="64" name="phone_code" /> <field eval="64" name="phone_code" />
<field name="vat_label">IRD/GST</field> <field name="vat_label">GST</field>
</record> </record>
<record id="om" model="res.country"> <record id="om" model="res.country">
<field name="name">Oman</field> <field name="name">Oman</field>
@ -1298,7 +1300,7 @@
<record id="sl" model="res.country"> <record id="sl" model="res.country">
<field name="name">Sierra Leone</field> <field name="name">Sierra Leone</field>
<field name="code">sl</field> <field name="code">sl</field>
<field name="currency_id" ref="SLL" /> <field name="currency_id" ref="SLE" />
<field eval="232" name="phone_code" /> <field eval="232" name="phone_code" />
</record> </record>
<record id="sm" model="res.country"> <record id="sm" model="res.country">
@ -1470,6 +1472,7 @@
<field name="code">ug</field> <field name="code">ug</field>
<field name="currency_id" ref="UGX" /> <field name="currency_id" ref="UGX" />
<field eval="256" name="phone_code" /> <field eval="256" name="phone_code" />
<field name="vat_label">TIN</field>
</record> </record>
<record id="uk" model="res.country"> <record id="uk" model="res.country">
<field name="name">United Kingdom</field> <field name="name">United Kingdom</field>
@ -1499,6 +1502,7 @@
<field name="code">uy</field> <field name="code">uy</field>
<field name="currency_id" ref="UYU" /> <field name="currency_id" ref="UYU" />
<field eval="598" name="phone_code" /> <field eval="598" name="phone_code" />
<field name="vat_label">RUT</field>
</record> </record>
<record id="uz" model="res.country"> <record id="uz" model="res.country">
<field name="name">Uzbekistan</field> <field name="name">Uzbekistan</field>
@ -1585,6 +1589,7 @@
<field name="code">zm</field> <field name="code">zm</field>
<field name="currency_id" ref="ZMW" /> <field name="currency_id" ref="ZMW" />
<field eval="260" name="phone_code" /> <field eval="260" name="phone_code" />
<field name="vat_label">TPIN</field>
</record> </record>
<record id="zw" model="res.country"> <record id="zw" model="res.country">
<field name="name">Zimbabwe</field> <field name="name">Zimbabwe</field>
@ -1600,7 +1605,7 @@
</record> </record>
<record id="europe" model="res.country.group"> <record id="europe" model="res.country.group">
<field name="name">Europe</field> <field name="name">European Union</field>
<field name="country_ids" eval="[Command.set([ <field name="country_ids" eval="[Command.set([
ref('at'),ref('be'),ref('bg'),ref('hr'),ref('cy'), ref('at'),ref('be'),ref('bg'),ref('hr'),ref('cy'),
ref('cz'),ref('dk'),ref('ee'),ref('fi'),ref('fr'), ref('cz'),ref('dk'),ref('ee'),ref('fi'),ref('fr'),
@ -1637,6 +1642,12 @@
<field name="name">Gulf Cooperation Council (GCC)</field> <field name="name">Gulf Cooperation Council (GCC)</field>
<field name="country_ids" eval="[(6,0, [ref('base.sa'), ref('base.ae'), ref('base.bh'), ref('base.om'), ref('base.qa'), ref('base.kw')])]"/> <field name="country_ids" eval="[(6,0, [ref('base.sa'), ref('base.ae'), ref('base.bh'), ref('base.om'), ref('base.qa'), ref('base.kw')])]"/>
</record> </record>
<record id="eurasian_economic_union" model="res.country.group">
<field name="name">Eurasian Economic Union</field>
<field name="country_ids" eval="[(6, 0, [ref('ru'),ref('by'),ref('am'),ref('kg'),ref('kz')])]"/>
</record>
<record id="ch_and_li" model="res.country.group"> <record id="ch_and_li" model="res.country.group">
<field name="name">Switzerland and Liechtenstein</field> <field name="name">Switzerland and Liechtenstein</field>
<field name="country_ids" eval="[Command.set([ref('ch'), ref('li')])]"/> <field name="country_ids" eval="[Command.set([ref('ch'), ref('li')])]"/>

View file

@ -931,7 +931,7 @@
<record id="KZT" model="res.currency"> <record id="KZT" model="res.currency">
<field name="name">KZT</field> <field name="name">KZT</field>
<field name="full_name">Kazakhstani tenge</field> <field name="full_name">Kazakhstani tenge</field>
<field name="symbol">лв</field> <field name="symbol"></field>
<field name="rounding">0.01</field> <field name="rounding">0.01</field>
<field name="active" eval="False"/> <field name="active" eval="False"/>
<field name="currency_unit_label">Tenge</field> <field name="currency_unit_label">Tenge</field>
@ -1064,6 +1064,7 @@
<field name="symbol"></field> <field name="symbol"></field>
<field name="rounding">0.01</field> <field name="rounding">0.01</field>
<field name="active" eval="False"/> <field name="active" eval="False"/>
<field name="position">before</field>
<field name="currency_unit_label">Rufiyaa</field> <field name="currency_unit_label">Rufiyaa</field>
<field name="currency_subunit_label">Laari</field> <field name="currency_subunit_label">Laari</field>
</record> </record>
@ -1495,6 +1496,16 @@
<field name="currency_subunit_label">Cents</field> <field name="currency_subunit_label">Cents</field>
</record> </record>
<record id="SLE" model="res.currency">
<field name="name">SLE</field>
<field name="full_name">Sierra Leonean leone</field>
<field name="symbol">Le</field>
<field name="rounding">0.01</field>
<field name="active" eval="False"/>
<field name="currency_unit_label">Leone</field>
<field name="currency_subunit_label">Cents</field>
</record>
<record id="SCR" model="res.currency"> <record id="SCR" model="res.currency">
<field name="name">SCR</field> <field name="name">SCR</field>
<field name="full_name">Seychellois rupee</field> <field name="full_name">Seychellois rupee</field>
@ -1699,7 +1710,7 @@
<field name="name">UYI</field> <field name="name">UYI</field>
<field name="full_name">Uruguay Peso en Unidades Indexadas</field> <field name="full_name">Uruguay Peso en Unidades Indexadas</field>
<field name="symbol">$</field> <field name="symbol">$</field>
<field name="rounding">1</field> <field name="rounding">0.0001</field>
<field name="active" eval="False"/> <field name="active" eval="False"/>
<field name="currency_unit_label">Peso</field> <field name="currency_unit_label">Peso</field>
<field name="currency_subunit_label">centésimo</field> <field name="currency_subunit_label">centésimo</field>

View file

@ -1,19 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<odoo> <odoo>
<data noupdate="1"> <data noupdate="1">
<!-- The Following currency rates are considered as on 1st Jan,2010 against EUR. -->
<record forcecreate="0" id="rateUSD" model="res.currency.rate"> <record forcecreate="0" id="rateUSD" model="res.currency.rate">
<field name="rate">1.2834</field> <field name="rate">1.0</field>
<field name="currency_id" ref="USD"/> <field name="currency_id" ref="USD"/>
<field name="name">2010-01-01</field> <field name="name">2010-01-01</field>
</record> </record>
<record id="rateUSDbis" model="res.currency.rate">
<field name="rate">1.5289</field>
<field name="currency_id" ref="USD"/>
<field eval="time.strftime('%Y-01-01')" name="name"/>
</record>
<record forcecreate="0" id="rateVEF" model="res.currency.rate"> <record forcecreate="0" id="rateVEF" model="res.currency.rate">
<field name="rate">5.864</field> <field name="rate">5.864</field>
<field name="currency_id" ref="VEF"/> <field name="currency_id" ref="VEF"/>
@ -283,6 +276,12 @@
<field name="rate">28.36</field> <field name="rate">28.36</field>
</record> </record>
<record forcecreate="0" id="rateUYI" model="res.currency.rate">
<field name="currency_id" ref="UYI"/>
<field name="name">2023-09-13</field>
<field name="rate">5.7778</field>
</record>
<record forcecreate="0" id="rateAFN" model="res.currency.rate"> <record forcecreate="0" id="rateAFN" model="res.currency.rate">
<field name="currency_id" ref="AFN" /> <field name="currency_id" ref="AFN" />
<field name="name">2010-01-01</field> <field name="name">2010-01-01</field>
@ -754,7 +753,7 @@
<record forcecreate="0" id="rateEUR" model="res.currency.rate"> <record forcecreate="0" id="rateEUR" model="res.currency.rate">
<field name="currency_id" ref="EUR" /> <field name="currency_id" ref="EUR" />
<field name="name">2010-01-01</field> <field name="name">2010-01-01</field>
<field name="rate">1.0</field> <field name="rate">1.2834</field>
</record> </record>
<record forcecreate="0" id="rateVUV" model="res.currency.rate"> <record forcecreate="0" id="rateVUV" model="res.currency.rate">
@ -883,6 +882,12 @@
<field name="rate">5320.43478</field> <field name="rate">5320.43478</field>
</record> </record>
<record forcecreate="0" id="rateSLE" model="res.currency.rate">
<field name="currency_id" ref="SLE" />
<field name="name">2023-06-08</field>
<field name="rate">22.5847</field>
</record>
<record forcecreate="0" id="rateSCR" model="res.currency.rate"> <record forcecreate="0" id="rateSCR" model="res.currency.rate">
<field name="currency_id" ref="SCR" /> <field name="currency_id" ref="SCR" />
<field name="name">2010-01-01</field> <field name="name">2010-01-01</field>

View file

@ -38,10 +38,6 @@
<!-- <!--
Resource: res.partner Resource: res.partner
--> -->
<record id="main_partner" model="res.partner">
<field name="email">info@yourcompany.com</field>
<field name="website">www.example.com</field>
</record>
<record id="res_partner_1" model="res.partner"> <record id="res_partner_1" model="res.partner">
<field name="name">Wood Corner</field> <field name="name">Wood Corner</field>
<field eval="[Command.set([ref('res_partner_category_14'), ref('res_partner_category_12')])]" name="category_id"/> <field eval="[Command.set([ref('res_partner_category_14'), ref('res_partner_category_12')])]" name="category_id"/>
@ -55,6 +51,7 @@
<field name="phone">(623)-853-7197</field> <field name="phone">(623)-853-7197</field>
<field name="website">http://www.wood-corner.com</field> <field name="website">http://www.wood-corner.com</field>
<field name="image_1920" type="base64" file="base/static/img/res_partner_1-image.png"/> <field name="image_1920" type="base64" file="base/static/img/res_partner_1-image.png"/>
<field name="vat">US12345672</field>
</record> </record>
<record id="res_partner_2" model="res.partner"> <record id="res_partner_2" model="res.partner">
<field name="name">Deco Addict</field> <field name="name">Deco Addict</field>
@ -65,10 +62,11 @@
<field name="state_id" ref='state_us_5'/> <field name="state_id" ref='state_us_5'/>
<field name="zip">94523</field> <field name="zip">94523</field>
<field name="country_id" ref="base.us"/> <field name="country_id" ref="base.us"/>
<field name="email">deco.addict82@example.com</field> <field name="email">deco_addict@yourcompany.example.com</field>
<field name="phone">(603)-996-3829</field> <field name="phone">(603)-996-3829</field>
<field name="website">http://www.deco-addict.com</field> <field name="website">http://www.deco-addict.com</field>
<field name="image_1920" type="base64" file="base/static/img/res_partner_2-image.png"/> <field name="image_1920" type="base64" file="base/static/img/res_partner_2-image.png"/>
<field name="vat">US12345673</field>
</record> </record>
<record id="res_partner_3" model="res.partner"> <record id="res_partner_3" model="res.partner">
<field name="name">Gemini Furniture</field> <field name="name">Gemini Furniture</field>
@ -79,10 +77,11 @@
<field name="state_id" ref='state_us_5'/> <field name="state_id" ref='state_us_5'/>
<field name="zip">94535</field> <field name="zip">94535</field>
<field name="country_id" ref="base.us"/> <field name="country_id" ref="base.us"/>
<field name="email">gemini.furniture39@example.com</field> <field name="email">gemini_furniture@fake.geminifurniture.com</field>
<field name="phone">(941)-284-4875</field> <field name="phone">(941)-284-4875</field>
<field name="website">http://www.gemini-furniture.com/</field> <field name="website">http://www.gemini-furniture.com/</field>
<field name="image_1920" type="base64" file="base/static/img/res_partner_3-image.png"/> <field name="image_1920" type="base64" file="base/static/img/res_partner_3-image.png"/>
<field name="vat">US12345674</field>
</record> </record>
<record id="res_partner_4" model="res.partner"> <record id="res_partner_4" model="res.partner">
@ -98,6 +97,7 @@
<field name="phone">(803)-873-6126</field> <field name="phone">(803)-873-6126</field>
<field name="website">http://www.ready-mat.com/</field> <field name="website">http://www.ready-mat.com/</field>
<field name="image_1920" type="base64" file="base/static/img/res_partner_4-image.png"/> <field name="image_1920" type="base64" file="base/static/img/res_partner_4-image.png"/>
<field name="vat">US12345675</field>
</record> </record>
<record id="res_partner_10" model="res.partner"> <record id="res_partner_10" model="res.partner">
@ -111,6 +111,7 @@
<field name="email">jackson.group82@example.com</field> <field name="email">jackson.group82@example.com</field>
<field name="phone">(334)-502-1024</field> <field name="phone">(334)-502-1024</field>
<field name="image_1920" type="base64" file="base/static/img/res_partner_10-image.jpg"/> <field name="image_1920" type="base64" file="base/static/img/res_partner_10-image.jpg"/>
<field name="vat">US12345676</field>
</record> </record>
<record id="res_partner_12" model="res.partner"> <record id="res_partner_12" model="res.partner">
@ -126,6 +127,7 @@
<field name="email">azure.Interior24@example.com</field> <field name="email">azure.Interior24@example.com</field>
<field name="website">http://www.azure-interior.com</field> <field name="website">http://www.azure-interior.com</field>
<field name="image_1920" type="base64" file="base/static/img/res_partner_12-image.png"/> <field name="image_1920" type="base64" file="base/static/img/res_partner_12-image.png"/>
<field name="vat">US12345677</field>
</record> </record>
<record id="res_partner_18" model="res.partner"> <record id="res_partner_18" model="res.partner">
@ -140,6 +142,7 @@
<field name="country_id" ref="base.us"/> <field name="country_id" ref="base.us"/>
<field name="website">http://www.lumber-inc.com</field> <field name="website">http://www.lumber-inc.com</field>
<field name="image_1920" type="base64" file="base/static/img/res_partner_18-image.png"/> <field name="image_1920" type="base64" file="base/static/img/res_partner_18-image.png"/>
<field name="vat">US12345678</field>
</record> </record>
<record id="res_partner_address_1" model="res.partner"> <record id="res_partner_address_1" model="res.partner">

View file

@ -8,7 +8,7 @@
<field name="company_name">YourCompany</field> <field name="company_name">YourCompany</field>
<field name="street">3575 Buena Vista Avenue</field> <field name="street">3575 Buena Vista Avenue</field>
<field name="city">Eugene</field> <field name="city">Eugene</field>
<field name="state_id" model="res.country.state" search="[('code','ilike','OR')]"/> <field name="state_id" model="res.country.state" search="[('code','=','OR')]"/>
<field name="zip">97401</field> <field name="zip">97401</field>
<field name="country_id" ref="us"/> <field name="country_id" ref="us"/>
<field name="tz">Europe/Brussels</field> <field name="tz">Europe/Brussels</field>
@ -16,18 +16,21 @@
<field name="phone">(441)-695-2334</field> <field name="phone">(441)-695-2334</field>
</record> </record>
<record id="main_partner" model="res.partner"> <!-- Only update if we don't have information coming from the database manager -->
<field name="name">YourCompany</field> <function model="res.partner" name="write">
<field name="company_name">YourCompany</field> <value eval="[ref('base.main_partner')]"/>
<field name="street">250 Executive Park Blvd, Suite 3400</field> <value eval="{
<field name="city">San Francisco</field> 'name': 'YourCompany',
<field name="zip">94134</field> 'street': '250 Executive Park Blvd, Suite 3400',
<field name='country_id' ref='base.us'/> 'city': 'San Francisco',
<field name='state_id' ref='state_us_5'/> 'zip': '94134',
<field name="phone">+1 555-555-5556</field> 'country_id': ref('base.us'),
<field name="email">info@yourcompany.example.com</field> 'state_id': ref('base.state_us_5'),
<field name="website">www.example.com</field> 'phone': '+1 555-555-5556',
</record> 'website': 'www.example.com',
'email': 'info@yourcompany.com',
} if obj(ref('base.main_partner')).name == 'My Company' else {}" model="res.partner"/>
</function>
<record id="user_demo" model="res.users"> <record id="user_demo" model="res.users">
<field name="partner_id" ref="base.partner_demo"/> <field name="partner_id" ref="base.partner_demo"/>
@ -36,7 +39,7 @@
<field name="signature" type="html"><span>-- <br/>+Mr Demo</span></field> <field name="signature" type="html"><span>-- <br/>+Mr Demo</span></field>
<field name="company_id" ref="main_company"/> <field name="company_id" ref="main_company"/>
<field name="groups_id" eval="[Command.set([ref('base.group_user'), ref('base.group_partner_manager'), ref('base.group_allow_export')])]"/> <field name="groups_id" eval="[Command.set([ref('base.group_user'), ref('base.group_partner_manager'), ref('base.group_allow_export')])]"/>
<field name="image_1920" type="base64" file="base/static/img/user_demo-image.jpg"/> <field name="image_1920" type="base64" file="base/static/img/user_demo-image.png"/>
</record> </record>
<record model="res.partner" id="base.partner_root"> <record model="res.partner" id="base.partner_root">
@ -54,7 +57,7 @@
<field name="phone">+1 555-555-5555</field> <field name="phone">+1 555-555-5555</field>
<field name="email">admin@yourcompany.example.com</field> <field name="email">admin@yourcompany.example.com</field>
<field name="tz">Europe/Brussels</field> <field name="tz">Europe/Brussels</field>
<field name="image_1920" type="base64" file="base/static/img/partner_root-image.jpg"/> <field name="image_1920" type="base64" file="base/static/img/partner_root-image.png"/>
</record> </record>
<record id="base.user_admin" model="res.users"> <record id="base.user_admin" model="res.users">
@ -71,7 +74,7 @@
<field name="zip">07002</field> <field name="zip">07002</field>
<field name="country_id" ref="base.us"/> <field name="country_id" ref="base.us"/>
<field name="company_name">YourCompany</field> <field name="company_name">YourCompany</field>
<field name="image_1920" type="base64" file="base/static/img/partner_demo_portal.jpg"/> <field name="image_1920" type="base64" file="base/static/img/partner_demo_portal.png"/>
<field name="phone">(683)-556-5104</field> <field name="phone">(683)-556-5104</field>
</record> </record>
<record id="demo_user0" model="res.users" context="{'no_reset_password': True}"> <record id="demo_user0" model="res.users" context="{'no_reset_password': True}">

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -43,6 +43,7 @@ from . import res_config
from . import res_currency from . import res_currency
from . import res_company from . import res_company
from . import res_users from . import res_users
from . import res_users_settings
from . import res_users_deletion from . import res_users_deletion
from . import decimal_precision from . import decimal_precision

View file

@ -24,80 +24,23 @@ except ImportError:
# `sassc` executable in the path. # `sassc` executable in the path.
libsass = None libsass = None
from rjsmin import jsmin as rjsmin
from odoo import release, SUPERUSER_ID, _ from odoo import release, SUPERUSER_ID, _
from odoo.http import request from odoo.http import request
from odoo.modules.module import get_resource_path
from odoo.tools import (func, misc, transpile_javascript, from odoo.tools import (func, misc, transpile_javascript,
is_odoo_module, SourceMapGenerator, profiler, is_odoo_module, SourceMapGenerator, profiler,
apply_inheritance_specs) apply_inheritance_specs)
from odoo.tools.misc import file_open, file_path, html_escape as escape from odoo.tools.constants import SCRIPT_EXTENSIONS, STYLE_EXTENSIONS
from odoo.tools.misc import file_open, file_path
from odoo.tools.pycompat import to_text from odoo.tools.pycompat import to_text
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
ANY_UNIQUE = '_' * 7
EXTENSIONS = (".js", ".css", ".scss", ".sass", ".less", ".xml") EXTENSIONS = (".js", ".css", ".scss", ".sass", ".less", ".xml")
class CompileError(RuntimeError): pass class CompileError(RuntimeError): pass
def rjsmin(script):
""" Minify js with a clever regex.
Taken from http://opensource.perlig.de/rjsmin (version 1.1.0)
Apache License, Version 2.0 """
def subber(match):
""" Substitution callback """
groups = match.groups()
return (
groups[0] or
groups[1] or
(groups[3] and (groups[2] + '\n')) or
groups[2] or
(groups[5] and "%s%s%s" % (
groups[4] and '\n' or '',
groups[5],
groups[6] and '\n' or '',
)) or
(groups[7] and '\n') or
(groups[8] and ' ') or
(groups[9] and ' ') or
(groups[10] and ' ') or
''
)
result = re.sub(
r'([^\047"\140/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^'
r'\r\n]|\r?\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^'
r'\r\n]|\r?\n|\r)[^"\\\r\n]*)*")|(?:\140[^\140\\]*(?:\\(?:[^\r\n'
r']|\r?\n|\r)[^\140\\]*)*\140))[^\047"\140/\000-\040]*)|(?<=[(,='
r':\[!&|?{};\r\n+*-])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*'
r'\*+(?:[^/*][^*]*\*+)*/))*(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-'
r'\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)*('
r'(?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*'
r'(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/))((?:[\000-\011'
r'\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:(?:('
r'?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*'
r']*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040&)+,.:;=?\]|}-]))?|'
r'(?<=[\000-#%-,./:-@\[-^\140{-~-]return)(?:[\000-\011\013\014\0'
r'16-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:((?:(?://[^\r'
r'\n]*)?[\r\n]))(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?'
r':[^/*][^*]*\*+)*/))*)*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^'
r'\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r'
r'\n]*)*/))((?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/'
r'*][^*]*\*+)*/))*(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013'
r'\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000'
r'-\040&)+,.:;=?\]|}-]))?|(?<=[^\000-!#%&(*,./:-@\[\\^{|~])(?:['
r'\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)'
r')*(?:((?:(?://[^\r\n]*)?[\r\n]))(?:[\000-\011\013\014\016-\040'
r']|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040"#%-\047'
r')*,./:-@\\-^\140|-~])|(?<=[^\000-#%-,./:-@\[-^\140{-~-])((?:['
r'\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)'
r'))+(?=[^\000-#%-,./:-@\[-^\140{-~-])|(?<=\+)((?:[\000-\011\013'
r'\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=\+)|(?<'
r'=-)((?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]'
r'*\*+)*/)))+(?=-)|(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*'
r'+(?:[^/*][^*]*\*+)*/))+|(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-'
r'\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+', subber, '\n%s\n' % script
).strip()
return result
class AssetError(Exception): class AssetError(Exception):
pass pass
@ -112,9 +55,9 @@ class AssetsBundle(object):
rx_preprocess_imports = re.compile(r"""(@import\s?['"]([^'"]+)['"](;?))""") rx_preprocess_imports = re.compile(r"""(@import\s?['"]([^'"]+)['"](;?))""")
rx_css_split = re.compile(r"\/\*\! ([a-f0-9-]+) \*\/") rx_css_split = re.compile(r"\/\*\! ([a-f0-9-]+) \*\/")
TRACKED_BUNDLES = ['web.assets_common', 'web.assets_backend'] TRACKED_BUNDLES = ['web.assets_web']
def __init__(self, name, files, env=None, css=True, js=True): def __init__(self, name, files, external_assets=(), env=None, css=True, js=True, debug_assets=False, rtl=False, assets_params=None):
""" """
:param name: bundle name :param name: bundle name
:param files: files to be added to the bundle :param files: files to be added to the bundle
@ -128,117 +71,89 @@ class AssetsBundle(object):
self.stylesheets = [] self.stylesheets = []
self.css_errors = [] self.css_errors = []
self.files = files self.files = files
self.user_direction = self.env['res.lang']._lang_get( self.rtl = rtl
self.env.context.get('lang') or self.env.user.lang self.assets_params = assets_params or {}
).direction self.has_css = css
self.has_js = js
self._checksum_cache = {}
self.is_debug_assets = debug_assets
self.external_assets = [
url
for url in external_assets
if (css and url.rpartition('.')[2] in STYLE_EXTENSIONS) or (js and url.rpartition('.')[2] in SCRIPT_EXTENSIONS)
]
# asset-wide html "media" attribute # asset-wide html "media" attribute
for f in files: for f in files:
extension = f['url'].rpartition('.')[2]
params = {
'url': f['url'],
'filename': f['filename'],
'inline': f['content'],
'last_modified': None if self.is_debug_assets else f.get('last_modified'),
}
if css: if css:
if f['atype'] == 'text/sass': css_params = {
self.stylesheets.append(SassStylesheetAsset(self, url=f['url'], filename=f['filename'], inline=f['content'], media=f['media'], direction=self.user_direction)) 'rtl': self.rtl,
elif f['atype'] == 'text/scss': }
self.stylesheets.append(ScssStylesheetAsset(self, url=f['url'], filename=f['filename'], inline=f['content'], media=f['media'], direction=self.user_direction)) if extension == 'sass':
elif f['atype'] == 'text/less': self.stylesheets.append(SassStylesheetAsset(self, **params, **css_params))
self.stylesheets.append(LessStylesheetAsset(self, url=f['url'], filename=f['filename'], inline=f['content'], media=f['media'], direction=self.user_direction)) elif extension == 'scss':
elif f['atype'] == 'text/css': self.stylesheets.append(ScssStylesheetAsset(self, **params, **css_params))
self.stylesheets.append(StylesheetAsset(self, url=f['url'], filename=f['filename'], inline=f['content'], media=f['media'], direction=self.user_direction)) elif extension == 'less':
self.stylesheets.append(LessStylesheetAsset(self, **params, **css_params))
elif extension == 'css':
self.stylesheets.append(StylesheetAsset(self, **params, **css_params))
if js: if js:
if f['atype'] == 'text/javascript': if extension == 'js':
self.javascripts.append(JavascriptAsset(self, url=f['url'], filename=f['filename'], inline=f['content'])) self.javascripts.append(JavascriptAsset(self, **params))
elif f['atype'] == 'text/xml': elif extension == 'xml':
self.templates.append(XMLAsset(self, url=f['url'], filename=f['filename'], inline=f['content'])) self.templates.append(XMLAsset(self, **params))
def to_node(self, css=True, js=True, debug=False, async_load=False, defer_load=False, lazy_load=False): def get_links(self):
""" """
:returns [(tagName, attributes, content)] if the tag is auto close :returns a list of tuple. a tuple can be (url, None) or (None, inlineContent)
""" """
response = [] response = []
is_debug_assets = debug and 'assets' in debug
if css and self.stylesheets:
css_attachments = self.css(is_minified=not is_debug_assets) or []
for attachment in css_attachments:
if is_debug_assets:
href = self.get_debug_asset_url(extra='rtl/' if self.user_direction == 'rtl' else '',
name=css_attachments.name,
extension='')
else:
href = attachment.url
attr = dict([
["type", "text/css"],
["rel", "stylesheet"],
["href", href],
['data-asset-bundle', self.name],
['data-asset-version', self.version],
])
response.append(("link", attr, None))
if self.css_errors:
msg = '\n'.join(self.css_errors)
response.append(JavascriptAsset(self, inline=self.dialog_message(msg)).to_node())
response.append(StylesheetAsset(self, url="/web/static/lib/bootstrap/dist/css/bootstrap.css").to_node())
if js and self.javascripts: if self.has_css and self.stylesheets:
js_attachment = self.js(is_minified=not is_debug_assets) response.append(self.get_link('css'))
src = self.get_debug_asset_url(name=js_attachment.name, extension='') if is_debug_assets else js_attachment[0].url
attr = dict([
["async", "async" if async_load else None],
["defer", "defer" if defer_load or lazy_load else None],
["type", "text/javascript"],
["data-src" if lazy_load else "src", src],
['data-asset-bundle', self.name],
['data-asset-version', self.version],
])
response.append(("script", attr, None))
return response if self.has_js and self.javascripts:
response.append(self.get_link('js'))
@func.lazy_property return self.external_assets + response
def last_modified_combined(self):
"""Returns last modified date of linked files"""
# WebAsset are recreate here when a better solution would be to use self.stylesheets and self.javascripts
# We currently have no garanty that they are present since it will depends on js and css parameters
# last_modified is actually only usefull for the checksum and checksum should be extension specific since
# they are differents bundles. This will be a future work.
# changing the logic from max date to combined date to fix bundle invalidation issues. def get_link(self, asset_type):
assets = [WebAsset(self, url=f['url'], filename=f['filename'], inline=f['content']) unique = self.get_version(asset_type) if not self.is_debug_assets else 'debug'
for f in self.files extension = asset_type if self.is_debug_assets else f'min.{asset_type}'
if f['atype'] in ['text/sass', "text/scss", "text/less", "text/css", "text/javascript", "text/xml"]] return self.get_asset_url(unique=unique, extension=extension)
return ','.join(str(asset.last_modified) for asset in assets)
@func.lazy_property def get_version(self, asset_type):
def version(self): return self.get_checksum(asset_type)[0:7]
return self.checksum[0:7]
@func.lazy_property def get_checksum(self, asset_type):
def checksum(self):
""" """
Not really a full checksum. Not really a full checksum.
We compute a SHA512/256 on the rendered bundle + combined linked files last_modified date We compute a SHA512/256 on the rendered bundle + combined linked files last_modified date
""" """
check = u"%s%s" % (json.dumps(self.files, sort_keys=True), self.last_modified_combined) if asset_type not in self._checksum_cache:
return hashlib.sha512(check.encode('utf-8')).hexdigest()[:64] if asset_type == 'css':
assets = self.stylesheets
elif asset_type == 'js':
assets = self.javascripts + self.templates
else:
raise ValueError(f'Asset type {asset_type} not known')
def _get_asset_template_url(self): unique_descriptor = ','.join(asset.unique_descriptor for asset in assets)
return "/web/assets/{id}-{unique}/{extra}{name}{sep}{extension}"
def _get_asset_url_values(self, id, unique, extra, name, sep, extension): # extra can contain direction or/and website self._checksum_cache[asset_type] = hashlib.sha512(unique_descriptor.encode()).hexdigest()[:64]
return { return self._checksum_cache[asset_type]
'id': id,
'unique': unique,
'extra': extra,
'name': name,
'sep': sep,
'extension': extension,
}
def get_asset_url(self, id='%', unique='%', extra='', name='%', sep="%", extension='%'): def get_asset_url(self, unique=ANY_UNIQUE, extension='%', ignore_params=False):
return self._get_asset_template_url().format( direction = '.rtl' if self.is_css(extension) and self.rtl else ''
**self._get_asset_url_values(id=id, unique=unique, extra=extra, name=name, sep=sep, extension=extension) bundle_name = f"{self.name}{direction}.{extension}"
) return self.env['ir.asset']._get_asset_bundle_url(bundle_name, unique, self.assets_params, ignore_params)
def get_debug_asset_url(self, extra='', name='%', extension='%'):
return f"/web/assets/debug/{extra}{name}{extension}"
def _unlink_attachments(self, attachments): def _unlink_attachments(self, attachments):
""" Unlinks attachments without actually calling unlink, so that the ORM cache is not cleared. """ Unlinks attachments without actually calling unlink, so that the ORM cache is not cleared.
@ -251,10 +166,13 @@ class AssetsBundle(object):
self.env.cr.execute(f"""DELETE FROM {attachments._table} WHERE id IN ( self.env.cr.execute(f"""DELETE FROM {attachments._table} WHERE id IN (
SELECT id FROM {attachments._table} WHERE id in %s FOR NO KEY UPDATE SKIP LOCKED SELECT id FROM {attachments._table} WHERE id in %s FOR NO KEY UPDATE SKIP LOCKED
)""", [tuple(attachments.ids)]) )""", [tuple(attachments.ids)])
for file_path in to_delete: for fpath in to_delete:
attachments._file_delete(file_path) attachments._file_delete(fpath)
def clean_attachments(self, extension): def is_css(self, extension):
return extension in ['css', 'min.css', 'css.map']
def _clean_attachments(self, extension, keep_url):
""" Takes care of deleting any outdated ir.attachment records associated to a bundle before """ Takes care of deleting any outdated ir.attachment records associated to a bundle before
saving a fresh one. saving a fresh one.
@ -265,25 +183,23 @@ class AssetsBundle(object):
must exclude the current bundle. must exclude the current bundle.
""" """
ira = self.env['ir.attachment'] ira = self.env['ir.attachment']
url = self.get_asset_url( to_clean_pattern = self.get_asset_url(
extra='%s' % ('rtl/' if extension in ['css', 'min.css'] and self.user_direction == 'rtl' else ''), unique=ANY_UNIQUE,
name=self.name, extension=extension,
sep='',
extension='.%s' % extension
) )
domain = [ domain = [
('url', '=like', url), ('url', '=like', to_clean_pattern),
'!', ('url', '=like', self.get_asset_url(unique=self.version)) ('url', '!=', keep_url),
('public', '=', True),
] ]
attachments = ira.sudo().search(domain) attachments = ira.sudo().search(domain)
# avoid to invalidate cache if it's already empty (mainly useful for test) # avoid to invalidate cache if it's already empty (mainly useful for test)
if attachments: if attachments:
_logger.info('Deleting ir.attachment %s (from bundle %s)', attachments.ids, self.name) _logger.info('Deleting attachments %s (matching %s) because it was replaced with %s', attachments.ids, to_clean_pattern, keep_url)
self._unlink_attachments(attachments) self._unlink_attachments(attachments)
# force bundle invalidation on other workers # clear_cache was removed
self.env['ir.qweb'].clear_caches()
return True return True
@ -296,21 +212,17 @@ class AssetsBundle(object):
by file name and only return the one with the max id for each group. by file name and only return the one with the max id for each group.
:param extension: file extension (js, min.js, css) :param extension: file extension (js, min.js, css)
:param ignore_version: if ignore_version, the url contains a version => web/assets/%-%/name.extension :param ignore_version: if ignore_version, the url contains a version => web/assets/%/name.extension
(the second '%' corresponds to the version), (the second '%' corresponds to the version),
else: the url contains a version equal to that of the self.version else: the url contains a version equal to that of the self.get_version(type)
=> web/assets/%-self.version/name.extension. => web/assets/self.get_version(type)/name.extension.
""" """
unique = "%" if ignore_version else self.version unique = ANY_UNIQUE if ignore_version else self.get_version('css' if self.is_css(extension) else 'js')
url_pattern = self.get_asset_url( url_pattern = self.get_asset_url(
unique=unique, unique=unique,
extra='%s' % ('rtl/' if extension in ['css', 'min.css'] and self.user_direction == 'rtl' else ''), extension=extension,
name=self.name,
sep='',
extension='.%s' % extension
) )
self.env.cr.execute(""" query = """
SELECT max(id) SELECT max(id)
FROM ir_attachment FROM ir_attachment
WHERE create_uid = %s WHERE create_uid = %s
@ -320,22 +232,37 @@ class AssetsBundle(object):
AND public = true AND public = true
GROUP BY name GROUP BY name
ORDER BY name ORDER BY name
""", [SUPERUSER_ID, url_pattern])
attachment_ids = [r[0] for r in self.env.cr.fetchall()]
if not attachment_ids:
_logger.info('Failed to find attachment for assets %s', url_pattern)
return self.env['ir.attachment'].sudo().browse(attachment_ids)
def add_post_rollback(self):
""" """
In some rare cases it is possible that an attachment is created self.env.cr.execute(query, [SUPERUSER_ID, url_pattern])
during a transaction, added to the ormcache but the transaction
is rolled back, leading to 404 when getting the attachments. attachment_id = [r[0] for r in self.env.cr.fetchall()]
This postrollback hook will help fix this issue by clearing the if not attachment_id and not ignore_version:
cache if it is not committed. fallback_url_pattern = self.get_asset_url(
""" unique=unique,
self.env.cr.postrollback.add(self.env.registry._Registry__cache.clear) extension=extension,
ignore_params=True,
)
self.env.cr.execute(query, [SUPERUSER_ID, fallback_url_pattern])
similar_attachment_ids = [r[0] for r in self.env.cr.fetchall()]
if similar_attachment_ids:
similar = self.env['ir.attachment'].sudo().browse(similar_attachment_ids)
_logger.info('Found a similar attachment for %s, copying from %s', url_pattern, similar.url)
url = url_pattern
values = {
'name': similar.name,
'mimetype': similar.mimetype,
'res_model': 'ir.ui.view',
'res_id': False,
'type': 'binary',
'public': True,
'raw': similar.raw,
'url': url,
}
attachment = self.env['ir.attachment'].with_user(SUPERUSER_ID).create(values)
attachment_id = attachment.id
self._clean_attachments(extension, url)
return self.env['ir.attachment'].sudo().browse(attachment_id)
def save_attachment(self, extension, content): def save_attachment(self, extension, content):
"""Record the given bundle in an ir.attachment and delete """Record the given bundle in an ir.attachment and delete
@ -360,6 +287,11 @@ class AssetsBundle(object):
'application/json' if extension in ['js.map', 'css.map'] else 'application/json' if extension in ['js.map', 'css.map'] else
'application/javascript' 'application/javascript'
) )
unique = self.get_version('css' if self.is_css(extension) else 'js')
url = self.get_asset_url(
unique=unique,
extension=extension,
)
values = { values = {
'name': fname, 'name': fname,
'mimetype': mimetype, 'mimetype': mimetype,
@ -368,26 +300,13 @@ class AssetsBundle(object):
'type': 'binary', 'type': 'binary',
'public': True, 'public': True,
'raw': content.encode('utf8'), 'raw': content.encode('utf8'),
}
self.add_post_rollback()
attachment = ira.with_user(SUPERUSER_ID).create(values)
url = self.get_asset_url(
id=attachment.id,
unique=self.version,
extra='%s' % ('rtl/' if extension in ['css', 'min.css'] and self.user_direction == 'rtl' else ''),
name=fname,
sep='', # included in fname
extension=''
)
values = {
'url': url, 'url': url,
} }
attachment.write(values) attachment = ira.with_user(SUPERUSER_ID).create(values)
if self.env.context.get('commit_assetsbundle') is True: _logger.info('Generating a new asset bundle attachment %s (id:%s)', attachment.url, attachment.id)
self.env.cr.commit()
self.clean_attachments(extension) self._clean_attachments(extension, url)
# For end-user assets (common and backend), send a message on the bus # For end-user assets (common and backend), send a message on the bus
# to invite the user to refresh their browser # to invite the user to refresh their browser
@ -395,11 +314,12 @@ class AssetsBundle(object):
self.env['bus.bus']._sendone('broadcast', 'bundle_changed', { self.env['bus.bus']._sendone('broadcast', 'bundle_changed', {
'server_version': release.version # Needs to be dynamically imported 'server_version': release.version # Needs to be dynamically imported
}) })
_logger.debug('Asset Changed: bundle: %s -- version: %s', self.name, self.version) _logger.debug('Asset Changed: bundle: %s -- version: %s', self.name, unique)
return attachment return attachment
def js(self, is_minified=True): def js(self):
is_minified = not self.is_debug_assets
extension = 'min.js' if is_minified else 'js' extension = 'min.js' if is_minified else 'js'
js_attachment = self.get_attachments(extension) js_attachment = self.get_attachments(extension)
@ -417,11 +337,10 @@ class AssetsBundle(object):
* Templates * * Templates *
*******************************************/ *******************************************/
odoo.define('{self.name}.bundle.xml', function(require){{ odoo.define('{self.name}.bundle.xml', ['@web/core/registry'], function(require){{
'use strict'; 'use strict';
const {{ loadXML }} = require('@web/core/assets'); const {{ registry }} = require('@web/core/registry');
const templates = `{templates}`; registry.category(`xml_templates`).add(`{self.name}`, `{templates}`);
return loadXML(templates);
}});""") }});""")
if is_minified: if is_minified:
@ -443,7 +362,7 @@ class AssetsBundle(object):
or self.save_attachment('js.map', '') or self.save_attachment('js.map', '')
generator = SourceMapGenerator( generator = SourceMapGenerator(
source_root="/".join( source_root="/".join(
[".." for i in range(0, len(self.get_debug_asset_url(name=self.name).split("/")) - 2)] [".." for i in range(0, len(self.get_asset_url().split("/")) - 2)]
) + "/", ) + "/",
) )
content_bundle_list = [] content_bundle_list = []
@ -535,7 +454,7 @@ class AssetsBundle(object):
else: else:
raise ValueError(_("Module %r not loaded or inexistent (try to inherit %r), or templates of addon being loaded %r are misordered (template %r)", parent_addon, parent_name, addon, template_name)) raise ValueError(_("Module %r not loaded or inexistent (try to inherit %r), or templates of addon being loaded %r are misordered (template %r)", parent_addon, parent_name, addon, template_name))
if parent_name not in template_dict[parent_addon]: if parent_name not in template_dict[parent_addon]:
raise ValueError(_("Cannot create %r because the template to inherit %r is not found.") % (f'{addon}.{template_name}', f'{parent_addon}.{parent_name}')) raise ValueError(_("Cannot create %r because the template to inherit %r is not found.", '%s.%s' % (addon, template_name), '%s.%s' % (parent_addon, parent_name)))
# After several performance tests, we found out that deepcopy is the most efficient # After several performance tests, we found out that deepcopy is the most efficient
# solution in this case (compared with copy, xpath with '.' and stringifying). # solution in this case (compared with copy, xpath with '.' and stringifying).
@ -607,28 +526,51 @@ class AssetsBundle(object):
# Returns the string by removing the <root> tag. # Returns the string by removing the <root> tag.
return etree.tostring(root, encoding='unicode')[6:-7] return etree.tostring(root, encoding='unicode')[6:-7]
def css(self, is_minified=True): def css(self):
is_minified = not self.is_debug_assets
extension = 'min.css' if is_minified else 'css' extension = 'min.css' if is_minified else 'css'
attachments = self.get_attachments(extension) attachments = self.get_attachments(extension)
if not attachments: if attachments:
# get css content return attachments
css = self.preprocess_css()
if self.css_errors:
return self.get_attachments(extension, ignore_version=True)
matches = [] css = self.preprocess_css()
css = re.sub(self.rx_css_import, lambda matchobj: matches.append(matchobj.group(0)) and '', css) if self.css_errors:
error_message = '\n'.join(self.css_errors).replace('"', r'\"').replace('\n', r'\A').replace('*', r'\*')
previous_attachment = self.get_attachments(extension, ignore_version=True)
previous_css = previous_attachment.raw.decode() if previous_attachment else ''
css_error_message_header = '\n\n/* ## CSS error message ##*/'
previous_css = previous_css.split(css_error_message_header)[0]
css = css_error_message_header.join([
previous_css, """
body::before {
font-weight: bold;
content: "A css error occured, using an old style to render this page";
position: fixed;
left: 0;
bottom: 0;
z-index: 100000000000;
background-color: #C00;
color: #DDD;
}
if is_minified: css_error_message {
# move up all @import rules to the top content: "%s";
matches.append(css) }
css = u'\n'.join(matches) """ % error_message
])
return self.save_attachment(extension, css)
self.save_attachment(extension, css) matches = []
attachments = self.get_attachments(extension) css = re.sub(self.rx_css_import, lambda matchobj: matches.append(matchobj.group(0)) and '', css)
else:
return self.css_with_sourcemap(u'\n'.join(matches)) if is_minified:
return attachments # move up all @import rules to the top
matches.append(css)
css = u'\n'.join(matches)
return self.save_attachment(extension, css)
else:
return self.css_with_sourcemap(u'\n'.join(matches))
def css_with_sourcemap(self, content_import_rules): def css_with_sourcemap(self, content_import_rules):
"""Create the ir.attachment representing the not-minified content of the bundleCSS """Create the ir.attachment representing the not-minified content of the bundleCSS
@ -639,8 +581,7 @@ class AssetsBundle(object):
""" """
sourcemap_attachment = self.get_attachments('css.map') \ sourcemap_attachment = self.get_attachments('css.map') \
or self.save_attachment('css.map', '') or self.save_attachment('css.map', '')
debug_asset_url = self.get_debug_asset_url(name=self.name, debug_asset_url = self.get_asset_url(unique='debug')
extra='rtl/' if self.user_direction == 'rtl' else '')
generator = SourceMapGenerator( generator = SourceMapGenerator(
source_root="/".join( source_root="/".join(
[".." for i in range(0, len(debug_asset_url.split("/")) - 2)] [".." for i in range(0, len(debug_asset_url.split("/")) - 2)]
@ -660,7 +601,7 @@ class AssetsBundle(object):
content_bundle_list.append(content) content_bundle_list.append(content)
content_line_count += len(content.split("\n")) content_line_count += len(content.split("\n"))
content_bundle = '\n'.join(content_bundle_list) + f"\n//*# sourceMappingURL={sourcemap_attachment.url} */" content_bundle = '\n'.join(content_bundle_list) + f"\n/*# sourceMappingURL={sourcemap_attachment.url} */"
css_attachment = self.save_attachment('css', content_bundle) css_attachment = self.save_attachment('css', content_bundle)
generator._file = css_attachment.url generator._file = css_attachment.url
@ -670,111 +611,6 @@ class AssetsBundle(object):
return css_attachment return css_attachment
def dialog_message(self, message):
"""
Returns a JS script which shows a warning to the user on page load.
TODO: should be refactored to be a base js file whose code is extended
by related apps (web/website).
"""
return """
(function (message) {
'use strict';
if (window.__assetsBundleErrorSeen) {
return;
}
window.__assetsBundleErrorSeen = true;
if (document.readyState !== 'loading') {
onDOMContentLoaded();
} else {
window.addEventListener('DOMContentLoaded', () => onDOMContentLoaded());
}
async function onDOMContentLoaded() {
var odoo = window.top.odoo;
if (!odoo || !odoo.define) {
useAlert();
return;
}
// Wait for potential JS loading
await new Promise(resolve => {
const noLazyTimeout = setTimeout(() => resolve(), 10); // 10 since need to wait for promise resolutions of odoo.define
odoo.define('AssetsBundle.PotentialLazyLoading', function (require) {
'use strict';
const lazyloader = require('web.public.lazyloader');
clearTimeout(noLazyTimeout);
lazyloader.allScriptsLoaded.then(() => resolve());
});
});
var alertTimeout = setTimeout(useAlert, 10); // 10 since need to wait for promise resolutions of odoo.define
odoo.define('AssetsBundle.ErrorMessage', function (require) {
'use strict';
require('web.dom_ready');
var core = require('web.core');
var Dialog = require('web.Dialog');
var _t = core._t;
clearTimeout(alertTimeout);
new Dialog(null, {
title: _t("Style error"),
$content: $('<div/>')
.append($('<p/>', {text: _t("The style compilation failed, see the error below. Your recent actions may be the cause, please try reverting the changes you made.")}))
.append($('<pre/>', {html: message})),
}).open();
});
}
function useAlert() {
window.alert(message);
}
})("%s");
""" % message.replace('"', '\\"').replace('\n', '&NewLine;')
def _get_assets_domain_for_already_processed_css(self, assets):
""" Method to compute the attachments' domain to search the already process assets (css).
This method was created to be overridden.
"""
return [('url', 'in', list(assets.keys()))]
def is_css_preprocessed(self):
preprocessed = True
old_attachments = self.env['ir.attachment'].sudo()
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:
assets_domain = self._get_assets_domain_for_already_processed_css(assets)
attachments = self.env['ir.attachment'].sudo().search(assets_domain)
old_attachments += attachments
for attachment in attachments:
asset = assets[attachment.url]
if asset.last_modified > attachment['__last_update']:
outdated = True
break
if asset._content is None:
asset._content = (attachment.raw or b'').decode('utf8')
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.values()):
outdated = True
if outdated:
preprocessed = False
return preprocessed, old_attachments
def preprocess_css(self, debug=False, old_attachments=None): def preprocess_css(self, debug=False, old_attachments=None):
""" """
Checks if the bundle contains any sass/less content, then compiles it to css. Checks if the bundle contains any sass/less content, then compiles it to css.
@ -791,7 +627,7 @@ class AssetsBundle(object):
compiled += self.compile_css(assets[0].compile, source) compiled += self.compile_css(assets[0].compile, source)
# We want to run rtlcss on normal css, so merge it in compiled # We want to run rtlcss on normal css, so merge it in compiled
if self.user_direction == 'rtl': if self.rtl:
stylesheet_assets = [asset for asset in self.stylesheets if not isinstance(asset, (SassStylesheetAsset, ScssStylesheetAsset, LessStylesheetAsset))] 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 += '\n'.join([asset.get_source() for asset in stylesheet_assets])
compiled = self.run_rtlcss(compiled) compiled = self.run_rtlcss(compiled)
@ -861,7 +697,7 @@ class AssetsBundle(object):
except IOError: except IOError:
rtlcss = 'rtlcss' rtlcss = 'rtlcss'
cmd = [rtlcss, '-c', get_resource_path("base", "data/rtlcss.json"), '-'] cmd = [rtlcss, '-c', file_path("base/data/rtlcss.json"), '-']
try: try:
rtlcss = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE) rtlcss = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
@ -915,18 +751,17 @@ class AssetsBundle(object):
class WebAsset(object): class WebAsset(object):
html_url_format = '%s'
_content = None _content = None
_filename = None _filename = None
_ir_attach = None _ir_attach = None
_id = None _id = None
def __init__(self, bundle, inline=None, url=None, filename=None): def __init__(self, bundle, inline=None, url=None, filename=None, last_modified=None):
self.bundle = bundle self.bundle = bundle
self.inline = inline self.inline = inline
self._filename = filename self._filename = filename
self.url = url self.url = url
self.html_url_args = url self._last_modified = last_modified
if not inline and not url: if not inline and not url:
raise Exception("An asset should either be inlined or url linked, defined in bundle '%s'" % bundle.name) raise Exception("An asset should either be inlined or url linked, defined in bundle '%s'" % bundle.name)
@ -935,20 +770,16 @@ class WebAsset(object):
if self._id is None: self._id = str(uuid.uuid4()) if self._id is None: self._id = str(uuid.uuid4())
return self._id return self._id
@func.lazy_property
def unique_descriptor(self):
return f'{self.url or self.inline},{self.last_modified}'
@func.lazy_property @func.lazy_property
def name(self): def name(self):
return '<inline asset>' if self.inline else self.url return '<inline asset>' if self.inline else self.url
@property
def html_url(self):
return self.html_url_format % self.html_url_args
def stat(self): def stat(self):
if not (self.inline or self._filename or self._ir_attach): if not (self.inline or self._filename or self._ir_attach):
path = (segment for segment in self.url.split('/') if segment)
self._filename = get_resource_path(*path)
if self._filename:
return
try: try:
# Test url against ir.attachments # Test url against ir.attachments
self._ir_attach = self.bundle.env['ir.attachment'].sudo()._get_serve_attachment(self.url) self._ir_attach = self.bundle.env['ir.attachment'].sudo()._get_serve_attachment(self.url)
@ -956,20 +787,20 @@ class WebAsset(object):
except ValueError: except ValueError:
raise AssetNotFound("Could not find %s" % self.name) raise AssetNotFound("Could not find %s" % self.name)
def to_node(self): @property
raise NotImplementedError()
@func.lazy_property
def last_modified(self): def last_modified(self):
try: if self._last_modified is None:
self.stat() try:
if self._filename: self.stat()
return datetime.fromtimestamp(os.path.getmtime(self._filename)) except Exception: # most likely nor a file or an attachment, skip it
pass
if self._filename and self.bundle.is_debug_assets: # usually _last_modified should be set exept in debug=assets
self._last_modified = os.path.getmtime(self._filename)
elif self._ir_attach: elif self._ir_attach:
return self._ir_attach['__last_update'] self._last_modified = self._ir_attach.write_date.timestamp()
except Exception: if not self._last_modified:
pass self._last_modified = -1
return datetime(1970, 1, 1) return self._last_modified
@property @property
def content(self): def content(self):
@ -1004,11 +835,15 @@ class WebAsset(object):
class JavascriptAsset(WebAsset): class JavascriptAsset(WebAsset):
def __init__(self, bundle, inline=None, url=None, filename=None): def __init__(self, bundle, **kwargs):
super().__init__(bundle, inline, url, filename) super().__init__(bundle, **kwargs)
self._is_transpiled = None self._is_transpiled = None
self._converted_content = None self._converted_content = None
@property
def bundle_version(self):
return self.bundle.get_version('js')
@property @property
def is_transpiled(self): def is_transpiled(self):
if self._is_transpiled is None: if self._is_transpiled is None:
@ -1033,21 +868,6 @@ class JavascriptAsset(WebAsset):
except AssetError as e: except AssetError as e:
return u"console.error(%s);" % json.dumps(to_text(e)) return u"console.error(%s);" % json.dumps(to_text(e))
def to_node(self):
if self.url:
return ("script", dict([
["type", "text/javascript"],
["src", self.html_url],
['data-asset-bundle', self.bundle.name],
['data-asset-version', self.bundle.version],
]), None)
else:
return ("script", dict([
["type", "text/javascript"],
["charset", "utf-8"],
['data-asset-bundle', self.bundle.name],
['data-asset-version', self.bundle.version],
]), self.with_header())
def with_header(self, content=None, minimal=True): def with_header(self, content=None, minimal=True):
if minimal: if minimal:
@ -1078,24 +898,20 @@ class XMLAsset(WebAsset):
try: try:
content = super()._fetch_content() content = super()._fetch_content()
except AssetError as e: except AssetError as e:
return f'<error data-asset-bundle={self.bundle.name!r} data-asset-version={self.bundle.version!r}>{json.dumps(to_text(e))}</error>' return u"console.error(%s);" % json.dumps(to_text(e))
parser = etree.XMLParser(ns_clean=True, recover=True, remove_comments=True) parser = etree.XMLParser(ns_clean=True, remove_comments=True, resolve_entities=False)
root = etree.parse(io.BytesIO(content.encode('utf-8')), parser=parser).getroot() try:
root = etree.fromstring(content.encode('utf-8'), parser=parser)
except etree.XMLSyntaxError as e:
return f'<t t-name="parsing_error{self.url.replace("/","_")}"><parsererror>Invalid XML template: {self.url} \n {e.msg} </parsererror></t>'
if root.tag in ('templates', 'template'): if root.tag in ('templates', 'template'):
return ''.join(etree.tostring(el, encoding='unicode') for el in root) return ''.join(etree.tostring(el, encoding='unicode') for el in root)
return etree.tostring(root, encoding='unicode') return etree.tostring(root, encoding='unicode')
def to_node(self): @property
attributes = { def bundle_version(self):
'async': 'async', return self.bundle.get_version('js')
'defer': 'defer',
'type': 'text/xml',
'data-src': self.html_url,
'data-asset-bundle': self.bundle.name,
'data-asset-version': self.bundle.version,
}
return ("script", attributes, None)
def with_header(self, content=None): def with_header(self, content=None):
if content is None: if content is None:
@ -1128,21 +944,18 @@ class StylesheetAsset(WebAsset):
rx_sourceMap = re.compile(r'(/\*# sourceMappingURL=.*)', re.U) rx_sourceMap = re.compile(r'(/\*# sourceMappingURL=.*)', re.U)
rx_charset = re.compile(r'(@charset "[^"]+";)', re.U) rx_charset = re.compile(r'(@charset "[^"]+";)', re.U)
def __init__(self, *args, **kw): def __init__(self, *args, rtl=False, **kw):
self.media = kw.pop('media', None) self.rtl = rtl
self.direction = kw.pop('direction', None)
super().__init__(*args, **kw) super().__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 @property
def content(self): def bundle_version(self):
content = super().content return self.bundle.get_version('css')
if self.media:
content = '@media %s { %s }' % (self.media, content) @func.lazy_property
return content def unique_descriptor(self):
direction = (self.rtl and 'rtl') or 'ltr'
return f'{self.url or self.inline},{self.last_modified},{direction}'
def _fetch_content(self): def _fetch_content(self):
try: try:
@ -1184,35 +997,10 @@ class StylesheetAsset(WebAsset):
content = re.sub(r' *([{}]) *', r'\1', content) content = re.sub(r' *([{}]) *', r'\1', content)
return self.with_header(content) return self.with_header(content)
def to_node(self):
if self.url:
attr = dict([
["type", "text/css"],
["rel", "stylesheet"],
["href", self.html_url],
["media", escape(to_text(self.media)) if self.media else None],
['data-asset-bundle', self.bundle.name],
['data-asset-version', self.bundle.version],
])
return ("link", attr, None)
else:
attr = dict([
["type", "text/css"],
["media", escape(to_text(self.media)) if self.media else None],
['data-asset-bundle', self.bundle.name],
['data-asset-version', self.bundle.version],
])
return ("style", attr, self.with_header())
class PreprocessedCSS(StylesheetAsset): class PreprocessedCSS(StylesheetAsset):
rx_import = None rx_import = None
def __init__(self, *args, **kw):
super().__init__(*args, **kw)
self.html_url_args = tuple(self.url.rsplit('/', 1))
self.html_url_format = '%%s/%s%s/%%s.css' % ('rtl/' if self.direction == 'rtl' else '', self.bundle.name)
def get_command(self): def get_command(self):
raise NotImplementedError raise NotImplementedError
@ -1271,7 +1059,7 @@ class SassStylesheetAsset(PreprocessedCSS):
class ScssStylesheetAsset(PreprocessedCSS): class ScssStylesheetAsset(PreprocessedCSS):
@property @property
def bootstrap_path(self): def bootstrap_path(self):
return get_resource_path('web', 'static', 'lib', 'bootstrap', 'scss') return file_path('web/static/lib/bootstrap/scss')
precision = 8 precision = 8
output_style = 'expanded' output_style = 'expanded'

Some files were not shown because too many files have changed in this diff Show more