mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-18 03:32:08 +02:00
17.0 vanilla
This commit is contained in:
parent
2e65bf056a
commit
df627a6bba
328 changed files with 578149 additions and 759311 deletions
|
|
@ -16,8 +16,8 @@ __path__ = [
|
|||
]
|
||||
|
||||
import sys
|
||||
MIN_PY_VERSION = (3, 7)
|
||||
MAX_PY_VERSION = (3, 12)
|
||||
MIN_PY_VERSION = (3, 10)
|
||||
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."
|
||||
|
||||
#----------------------------------------------------------
|
||||
|
|
@ -68,26 +68,6 @@ import time
|
|||
if hasattr(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
|
||||
# ---------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -8,9 +8,6 @@ from . import report
|
|||
from . import wizard
|
||||
|
||||
|
||||
def post_init(cr, registry):
|
||||
def post_init(env):
|
||||
"""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)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ The kernel of Odoo, needed for all installation.
|
|||
'data/report_paperformat_data.xml',
|
||||
'data/res_country_data.xml',
|
||||
'data/ir_demo_data.xml',
|
||||
'data/ir_config_parameter_data.xml',
|
||||
'security/base_groups.xml',
|
||||
'security/base_security.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_export_language_views.xml',
|
||||
'wizard/base_partner_merge_views.xml',
|
||||
'data/ir_actions_data.xml',
|
||||
'data/ir_demo_failure_data.xml',
|
||||
'views/ir_profile_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_currency_views.xml',
|
||||
'views/res_users_views.xml',
|
||||
'views/res_users_identitycheck_views.xml',
|
||||
'views/ir_property_views.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/report_paperformat_views.xml',
|
||||
'views/onboarding_views.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'demo': [
|
||||
'data/res_company_demo.xml',
|
||||
'data/res_users_demo.xml',
|
||||
'data/res_partner_bank_demo.xml',
|
||||
'data/res_currency_demo.xml',
|
||||
'data/res_currency_rate_demo.xml',
|
||||
'data/res_bank_demo.xml',
|
||||
'data/res_partner_demo.xml',
|
||||
|
|
|
|||
|
|
@ -119,8 +119,8 @@ CREATE TABLE res_partner (
|
|||
---------------------------------
|
||||
-- Default data
|
||||
---------------------------------
|
||||
insert into res_currency (id, name, symbol) VALUES (1, 'EUR', '€');
|
||||
insert into ir_model_data (name, module, model, noupdate, res_id) VALUES ('EUR', 'base', 'res.currency', true, 1);
|
||||
insert into res_currency (id, name, symbol) VALUES (1, 'USD', '$');
|
||||
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);
|
||||
|
||||
insert into res_company (id, name, partner_id, currency_id, create_date) VALUES (1, 'My Company', 1, 1, now() at time zone 'UTC');
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -8,5 +8,16 @@
|
|||
<field name='interval_number'>1</field>
|
||||
<field name='interval_type'>days</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>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
</div>
|
||||
|
||||
<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"/>
|
||||
</footer>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -101,6 +101,11 @@
|
|||
<field name="sequence">14</field>
|
||||
</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">
|
||||
<field name="name">Field Service</field>
|
||||
<field name="parent_id" ref="module_category_services"/>
|
||||
|
|
|
|||
|
|
@ -142,6 +142,20 @@
|
|||
<field name="website">https://www.odoo.com/app/quality?utm_source=db&utm_medium=module</field>
|
||||
</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&utm_medium=module</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.module.module" id="base.module_sale_ebay">
|
||||
<field name="name">sale_ebay</field>
|
||||
<field name="shortdesc">eBay Connector</field>
|
||||
|
|
|
|||
|
|
@ -22,3 +22,8 @@ INSERT INTO ir_config_parameter (key, value)
|
|||
VALUES ('database.is_neutralized', true)
|
||||
ON CONFLICT (key) DO
|
||||
UPDATE SET value = true;
|
||||
|
||||
-- deactivate webhooks
|
||||
UPDATE ir_act_server
|
||||
SET webhook_url = 'neutralization - disable webhook'
|
||||
WHERE state = 'webhook';
|
||||
|
|
|
|||
|
|
@ -467,22 +467,22 @@ state_es_va,es,"Valladolid","VA"
|
|||
state_es_bi,es,"Bizkaia (Vizcaya)","BI"
|
||||
state_es_za,es,"Zamora","ZA"
|
||||
state_es_z,es,"Zaragoza","Z"
|
||||
state_my_jhr,my,"Johor","JHR"
|
||||
state_my_kdh,my,"Kedah","KDH"
|
||||
state_my_ktn,my,"Kelantan","KTN"
|
||||
state_my_kul,my,"Kuala Lumpur","KUL"
|
||||
state_my_lbn,my,"Labuan","LBN"
|
||||
state_my_mlk,my,"Melaka","MLK"
|
||||
state_my_nsn,my,"Negeri Sembilan","NSN"
|
||||
state_my_phg,my,"Pahang","PHG"
|
||||
state_my_prk,my,"Perak","PRK"
|
||||
state_my_pls,my,"Perlis","PLS"
|
||||
state_my_png,my,"Pulau Pinang","PNG"
|
||||
state_my_pjy,my,"Putrajaya","PJY"
|
||||
state_my_sbh,my,"Sabah","SBH"
|
||||
state_my_swk,my,"Sarawak","SWK"
|
||||
state_my_sgr,my,"Selangor","SGR"
|
||||
state_my_trg,my,"Terengganu","TRG"
|
||||
state_my_jhr,my,"Johor","MY-01"
|
||||
state_my_kdh,my,"Kedah","MY-02"
|
||||
state_my_ktn,my,"Kelantan","MY-03"
|
||||
state_my_kul,my,"Kuala Lumpur","MY-14"
|
||||
state_my_lbn,my,"Labuan","MY-15"
|
||||
state_my_mlk,my,"Melaka","MY-04"
|
||||
state_my_nsn,my,"Negeri Sembilan","MY-05"
|
||||
state_my_phg,my,"Pahang","MY-06"
|
||||
state_my_prk,my,"Perak","MY-08"
|
||||
state_my_pls,my,"Perlis","MY-09"
|
||||
state_my_png,my,"Pulau Pinang","MY-07"
|
||||
state_my_pjy,my,"Putrajaya","MY-16"
|
||||
state_my_sbh,my,"Sabah","MY-12"
|
||||
state_my_swk,my,"Sarawak","MY-13"
|
||||
state_my_sgr,my,"Selangor","MY-10"
|
||||
state_my_trg,my,"Terengganu","MY-11"
|
||||
state_mx_ags,mx,"Aguascalientes","AGU"
|
||||
state_mx_bc,mx,"Baja California","BCN"
|
||||
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_92,sa,"Zilfi","ZUL"
|
||||
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,8 +1,8 @@
|
|||
"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_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_SY","Arabic (Syria) / الْعَرَبيّة","ar_SY","ar_SY","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 %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_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"
|
||||
|
|
@ -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_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_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_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"
|
||||
|
|
@ -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_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_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_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"
|
||||
|
|
@ -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_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_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_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"
|
||||
|
|
@ -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_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_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_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"
|
||||
|
|
|
|||
|
|
|
@ -4,8 +4,7 @@
|
|||
<record id="main_company" model="res.company">
|
||||
<field name="name">My Company</field>
|
||||
<field name="partner_id" ref="main_partner"/>
|
||||
<field name="currency_id" ref="base.EUR"/>
|
||||
<field name="favicon" model="res.company" eval="obj()._get_default_favicon(original=True)"/>
|
||||
<field name="currency_id" ref="base.USD"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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 name="currency_id" ref="CAD" />
|
||||
<field eval="1" name="phone_code" />
|
||||
<field name="vat_label">HST</field>
|
||||
<field name="vat_label">GST/HST number</field>
|
||||
</record>
|
||||
<record id="cc" model="res.country">
|
||||
<field name="name">Cocos (Keeling) Islands</field>
|
||||
|
|
@ -412,6 +412,8 @@
|
|||
<field name="currency_id" ref="USD" />
|
||||
<field eval="593" name="phone_code" />
|
||||
<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 id="ee" model="res.country">
|
||||
<field name="name">Estonia</field>
|
||||
|
|
@ -441,7 +443,7 @@
|
|||
<record id="es" model="res.country">
|
||||
<field name="name">Spain</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 eval="34" name="phone_code" />
|
||||
<field name="vat_label">VAT</field>
|
||||
|
|
@ -1095,7 +1097,7 @@
|
|||
<field name="code">nz</field>
|
||||
<field name="currency_id" ref="NZD" />
|
||||
<field eval="64" name="phone_code" />
|
||||
<field name="vat_label">IRD/GST</field>
|
||||
<field name="vat_label">GST</field>
|
||||
</record>
|
||||
<record id="om" model="res.country">
|
||||
<field name="name">Oman</field>
|
||||
|
|
@ -1298,7 +1300,7 @@
|
|||
<record id="sl" model="res.country">
|
||||
<field name="name">Sierra Leone</field>
|
||||
<field name="code">sl</field>
|
||||
<field name="currency_id" ref="SLL" />
|
||||
<field name="currency_id" ref="SLE" />
|
||||
<field eval="232" name="phone_code" />
|
||||
</record>
|
||||
<record id="sm" model="res.country">
|
||||
|
|
@ -1470,6 +1472,7 @@
|
|||
<field name="code">ug</field>
|
||||
<field name="currency_id" ref="UGX" />
|
||||
<field eval="256" name="phone_code" />
|
||||
<field name="vat_label">TIN</field>
|
||||
</record>
|
||||
<record id="uk" model="res.country">
|
||||
<field name="name">United Kingdom</field>
|
||||
|
|
@ -1499,6 +1502,7 @@
|
|||
<field name="code">uy</field>
|
||||
<field name="currency_id" ref="UYU" />
|
||||
<field eval="598" name="phone_code" />
|
||||
<field name="vat_label">RUT</field>
|
||||
</record>
|
||||
<record id="uz" model="res.country">
|
||||
<field name="name">Uzbekistan</field>
|
||||
|
|
@ -1585,6 +1589,7 @@
|
|||
<field name="code">zm</field>
|
||||
<field name="currency_id" ref="ZMW" />
|
||||
<field eval="260" name="phone_code" />
|
||||
<field name="vat_label">TPIN</field>
|
||||
</record>
|
||||
<record id="zw" model="res.country">
|
||||
<field name="name">Zimbabwe</field>
|
||||
|
|
@ -1600,7 +1605,7 @@
|
|||
</record>
|
||||
|
||||
<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([
|
||||
ref('at'),ref('be'),ref('bg'),ref('hr'),ref('cy'),
|
||||
ref('cz'),ref('dk'),ref('ee'),ref('fi'),ref('fr'),
|
||||
|
|
@ -1637,6 +1642,12 @@
|
|||
<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')])]"/>
|
||||
</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">
|
||||
<field name="name">Switzerland and Liechtenstein</field>
|
||||
<field name="country_ids" eval="[Command.set([ref('ch'), ref('li')])]"/>
|
||||
|
|
|
|||
|
|
@ -931,7 +931,7 @@
|
|||
<record id="KZT" model="res.currency">
|
||||
<field name="name">KZT</field>
|
||||
<field name="full_name">Kazakhstani tenge</field>
|
||||
<field name="symbol">лв</field>
|
||||
<field name="symbol">₸</field>
|
||||
<field name="rounding">0.01</field>
|
||||
<field name="active" eval="False"/>
|
||||
<field name="currency_unit_label">Tenge</field>
|
||||
|
|
@ -1064,6 +1064,7 @@
|
|||
<field name="symbol">.ރ</field>
|
||||
<field name="rounding">0.01</field>
|
||||
<field name="active" eval="False"/>
|
||||
<field name="position">before</field>
|
||||
<field name="currency_unit_label">Rufiyaa</field>
|
||||
<field name="currency_subunit_label">Laari</field>
|
||||
</record>
|
||||
|
|
@ -1495,6 +1496,16 @@
|
|||
<field name="currency_subunit_label">Cents</field>
|
||||
</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">
|
||||
<field name="name">SCR</field>
|
||||
<field name="full_name">Seychellois rupee</field>
|
||||
|
|
@ -1699,7 +1710,7 @@
|
|||
<field name="name">UYI</field>
|
||||
<field name="full_name">Uruguay Peso en Unidades Indexadas</field>
|
||||
<field name="symbol">$</field>
|
||||
<field name="rounding">1</field>
|
||||
<field name="rounding">0.0001</field>
|
||||
<field name="active" eval="False"/>
|
||||
<field name="currency_unit_label">Peso</field>
|
||||
<field name="currency_subunit_label">centésimo</field>
|
||||
|
|
|
|||
|
|
@ -1,19 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<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">
|
||||
<field name="rate">1.2834</field>
|
||||
<field name="rate">1.0</field>
|
||||
<field name="currency_id" ref="USD"/>
|
||||
<field name="name">2010-01-01</field>
|
||||
</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">
|
||||
<field name="rate">5.864</field>
|
||||
<field name="currency_id" ref="VEF"/>
|
||||
|
|
@ -283,6 +276,12 @@
|
|||
<field name="rate">28.36</field>
|
||||
</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">
|
||||
<field name="currency_id" ref="AFN" />
|
||||
<field name="name">2010-01-01</field>
|
||||
|
|
@ -754,7 +753,7 @@
|
|||
<record forcecreate="0" id="rateEUR" model="res.currency.rate">
|
||||
<field name="currency_id" ref="EUR" />
|
||||
<field name="name">2010-01-01</field>
|
||||
<field name="rate">1.0</field>
|
||||
<field name="rate">1.2834</field>
|
||||
</record>
|
||||
|
||||
<record forcecreate="0" id="rateVUV" model="res.currency.rate">
|
||||
|
|
@ -883,6 +882,12 @@
|
|||
<field name="rate">5320.43478</field>
|
||||
</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">
|
||||
<field name="currency_id" ref="SCR" />
|
||||
<field name="name">2010-01-01</field>
|
||||
|
|
|
|||
|
|
@ -38,10 +38,6 @@
|
|||
<!--
|
||||
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">
|
||||
<field name="name">Wood Corner</field>
|
||||
<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="website">http://www.wood-corner.com</field>
|
||||
<field name="image_1920" type="base64" file="base/static/img/res_partner_1-image.png"/>
|
||||
<field name="vat">US12345672</field>
|
||||
</record>
|
||||
<record id="res_partner_2" model="res.partner">
|
||||
<field name="name">Deco Addict</field>
|
||||
|
|
@ -65,10 +62,11 @@
|
|||
<field name="state_id" ref='state_us_5'/>
|
||||
<field name="zip">94523</field>
|
||||
<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="website">http://www.deco-addict.com</field>
|
||||
<field name="image_1920" type="base64" file="base/static/img/res_partner_2-image.png"/>
|
||||
<field name="vat">US12345673</field>
|
||||
</record>
|
||||
<record id="res_partner_3" model="res.partner">
|
||||
<field name="name">Gemini Furniture</field>
|
||||
|
|
@ -79,10 +77,11 @@
|
|||
<field name="state_id" ref='state_us_5'/>
|
||||
<field name="zip">94535</field>
|
||||
<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="website">http://www.gemini-furniture.com/</field>
|
||||
<field name="image_1920" type="base64" file="base/static/img/res_partner_3-image.png"/>
|
||||
<field name="vat">US12345674</field>
|
||||
</record>
|
||||
|
||||
<record id="res_partner_4" model="res.partner">
|
||||
|
|
@ -98,6 +97,7 @@
|
|||
<field name="phone">(803)-873-6126</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="vat">US12345675</field>
|
||||
</record>
|
||||
|
||||
<record id="res_partner_10" model="res.partner">
|
||||
|
|
@ -111,6 +111,7 @@
|
|||
<field name="email">jackson.group82@example.com</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="vat">US12345676</field>
|
||||
</record>
|
||||
|
||||
<record id="res_partner_12" model="res.partner">
|
||||
|
|
@ -126,6 +127,7 @@
|
|||
<field name="email">azure.Interior24@example.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="vat">US12345677</field>
|
||||
</record>
|
||||
|
||||
<record id="res_partner_18" model="res.partner">
|
||||
|
|
@ -140,6 +142,7 @@
|
|||
<field name="country_id" ref="base.us"/>
|
||||
<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="vat">US12345678</field>
|
||||
</record>
|
||||
|
||||
<record id="res_partner_address_1" model="res.partner">
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
<field name="company_name">YourCompany</field>
|
||||
<field name="street">3575 Buena Vista Avenue</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="country_id" ref="us"/>
|
||||
<field name="tz">Europe/Brussels</field>
|
||||
|
|
@ -16,18 +16,21 @@
|
|||
<field name="phone">(441)-695-2334</field>
|
||||
</record>
|
||||
|
||||
<record id="main_partner" model="res.partner">
|
||||
<field name="name">YourCompany</field>
|
||||
<field name="company_name">YourCompany</field>
|
||||
<field name="street">250 Executive Park Blvd, Suite 3400</field>
|
||||
<field name="city">San Francisco</field>
|
||||
<field name="zip">94134</field>
|
||||
<field name='country_id' ref='base.us'/>
|
||||
<field name='state_id' ref='state_us_5'/>
|
||||
<field name="phone">+1 555-555-5556</field>
|
||||
<field name="email">info@yourcompany.example.com</field>
|
||||
<field name="website">www.example.com</field>
|
||||
</record>
|
||||
<!-- Only update if we don't have information coming from the database manager -->
|
||||
<function model="res.partner" name="write">
|
||||
<value eval="[ref('base.main_partner')]"/>
|
||||
<value eval="{
|
||||
'name': 'YourCompany',
|
||||
'street': '250 Executive Park Blvd, Suite 3400',
|
||||
'city': 'San Francisco',
|
||||
'zip': '94134',
|
||||
'country_id': ref('base.us'),
|
||||
'state_id': ref('base.state_us_5'),
|
||||
'phone': '+1 555-555-5556',
|
||||
'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">
|
||||
<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="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="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 model="res.partner" id="base.partner_root">
|
||||
|
|
@ -54,7 +57,7 @@
|
|||
<field name="phone">+1 555-555-5555</field>
|
||||
<field name="email">admin@yourcompany.example.com</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 id="base.user_admin" model="res.users">
|
||||
|
|
@ -71,7 +74,7 @@
|
|||
<field name="zip">07002</field>
|
||||
<field name="country_id" ref="base.us"/>
|
||||
<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>
|
||||
</record>
|
||||
<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
|
|
@ -43,6 +43,7 @@ from . import res_config
|
|||
from . import res_currency
|
||||
from . import res_company
|
||||
from . import res_users
|
||||
from . import res_users_settings
|
||||
from . import res_users_deletion
|
||||
|
||||
from . import decimal_precision
|
||||
|
|
|
|||
|
|
@ -24,80 +24,23 @@ except ImportError:
|
|||
# `sassc` executable in the path.
|
||||
libsass = None
|
||||
|
||||
from rjsmin import jsmin as rjsmin
|
||||
|
||||
from odoo import release, SUPERUSER_ID, _
|
||||
from odoo.http import request
|
||||
from odoo.modules.module import get_resource_path
|
||||
from odoo.tools import (func, misc, transpile_javascript,
|
||||
is_odoo_module, SourceMapGenerator, profiler,
|
||||
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
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
ANY_UNIQUE = '_' * 7
|
||||
EXTENSIONS = (".js", ".css", ".scss", ".sass", ".less", ".xml")
|
||||
|
||||
|
||||
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):
|
||||
pass
|
||||
|
|
@ -112,9 +55,9 @@ class AssetsBundle(object):
|
|||
rx_preprocess_imports = re.compile(r"""(@import\s?['"]([^'"]+)['"](;?))""")
|
||||
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 files: files to be added to the bundle
|
||||
|
|
@ -128,117 +71,89 @@ class AssetsBundle(object):
|
|||
self.stylesheets = []
|
||||
self.css_errors = []
|
||||
self.files = files
|
||||
self.user_direction = self.env['res.lang']._lang_get(
|
||||
self.env.context.get('lang') or self.env.user.lang
|
||||
).direction
|
||||
self.rtl = rtl
|
||||
self.assets_params = assets_params or {}
|
||||
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
|
||||
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 f['atype'] == 'text/sass':
|
||||
self.stylesheets.append(SassStylesheetAsset(self, url=f['url'], filename=f['filename'], inline=f['content'], media=f['media'], direction=self.user_direction))
|
||||
elif f['atype'] == 'text/scss':
|
||||
self.stylesheets.append(ScssStylesheetAsset(self, url=f['url'], filename=f['filename'], inline=f['content'], media=f['media'], direction=self.user_direction))
|
||||
elif f['atype'] == 'text/less':
|
||||
self.stylesheets.append(LessStylesheetAsset(self, url=f['url'], filename=f['filename'], inline=f['content'], media=f['media'], direction=self.user_direction))
|
||||
elif f['atype'] == 'text/css':
|
||||
self.stylesheets.append(StylesheetAsset(self, url=f['url'], filename=f['filename'], inline=f['content'], media=f['media'], direction=self.user_direction))
|
||||
css_params = {
|
||||
'rtl': self.rtl,
|
||||
}
|
||||
if extension == 'sass':
|
||||
self.stylesheets.append(SassStylesheetAsset(self, **params, **css_params))
|
||||
elif extension == 'scss':
|
||||
self.stylesheets.append(ScssStylesheetAsset(self, **params, **css_params))
|
||||
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 f['atype'] == 'text/javascript':
|
||||
self.javascripts.append(JavascriptAsset(self, url=f['url'], filename=f['filename'], inline=f['content']))
|
||||
elif f['atype'] == 'text/xml':
|
||||
self.templates.append(XMLAsset(self, url=f['url'], filename=f['filename'], inline=f['content']))
|
||||
if extension == 'js':
|
||||
self.javascripts.append(JavascriptAsset(self, **params))
|
||||
elif extension == 'xml':
|
||||
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 = []
|
||||
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:
|
||||
js_attachment = self.js(is_minified=not is_debug_assets)
|
||||
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))
|
||||
if self.has_css and self.stylesheets:
|
||||
response.append(self.get_link('css'))
|
||||
|
||||
return response
|
||||
if self.has_js and self.javascripts:
|
||||
response.append(self.get_link('js'))
|
||||
|
||||
@func.lazy_property
|
||||
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.
|
||||
return self.external_assets + response
|
||||
|
||||
# changing the logic from max date to combined date to fix bundle invalidation issues.
|
||||
assets = [WebAsset(self, url=f['url'], filename=f['filename'], inline=f['content'])
|
||||
for f in self.files
|
||||
if f['atype'] in ['text/sass', "text/scss", "text/less", "text/css", "text/javascript", "text/xml"]]
|
||||
return ','.join(str(asset.last_modified) for asset in assets)
|
||||
def get_link(self, asset_type):
|
||||
unique = self.get_version(asset_type) if not self.is_debug_assets else 'debug'
|
||||
extension = asset_type if self.is_debug_assets else f'min.{asset_type}'
|
||||
return self.get_asset_url(unique=unique, extension=extension)
|
||||
|
||||
@func.lazy_property
|
||||
def version(self):
|
||||
return self.checksum[0:7]
|
||||
def get_version(self, asset_type):
|
||||
return self.get_checksum(asset_type)[0:7]
|
||||
|
||||
@func.lazy_property
|
||||
def checksum(self):
|
||||
def get_checksum(self, asset_type):
|
||||
"""
|
||||
Not really a full checksum.
|
||||
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)
|
||||
return hashlib.sha512(check.encode('utf-8')).hexdigest()[:64]
|
||||
if asset_type not in self._checksum_cache:
|
||||
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):
|
||||
return "/web/assets/{id}-{unique}/{extra}{name}{sep}{extension}"
|
||||
unique_descriptor = ','.join(asset.unique_descriptor for asset in assets)
|
||||
|
||||
def _get_asset_url_values(self, id, unique, extra, name, sep, extension): # extra can contain direction or/and website
|
||||
return {
|
||||
'id': id,
|
||||
'unique': unique,
|
||||
'extra': extra,
|
||||
'name': name,
|
||||
'sep': sep,
|
||||
'extension': extension,
|
||||
}
|
||||
self._checksum_cache[asset_type] = hashlib.sha512(unique_descriptor.encode()).hexdigest()[:64]
|
||||
return self._checksum_cache[asset_type]
|
||||
|
||||
def get_asset_url(self, id='%', unique='%', extra='', name='%', sep="%", extension='%'):
|
||||
return self._get_asset_template_url().format(
|
||||
**self._get_asset_url_values(id=id, unique=unique, extra=extra, name=name, sep=sep, extension=extension)
|
||||
)
|
||||
|
||||
def get_debug_asset_url(self, extra='', name='%', extension='%'):
|
||||
return f"/web/assets/debug/{extra}{name}{extension}"
|
||||
def get_asset_url(self, unique=ANY_UNIQUE, extension='%', ignore_params=False):
|
||||
direction = '.rtl' if self.is_css(extension) and self.rtl else ''
|
||||
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 _unlink_attachments(self, attachments):
|
||||
""" 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 (
|
||||
SELECT id FROM {attachments._table} WHERE id in %s FOR NO KEY UPDATE SKIP LOCKED
|
||||
)""", [tuple(attachments.ids)])
|
||||
for file_path in to_delete:
|
||||
attachments._file_delete(file_path)
|
||||
for fpath in to_delete:
|
||||
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
|
||||
saving a fresh one.
|
||||
|
||||
|
|
@ -265,25 +183,23 @@ class AssetsBundle(object):
|
|||
must exclude the current bundle.
|
||||
"""
|
||||
ira = self.env['ir.attachment']
|
||||
url = self.get_asset_url(
|
||||
extra='%s' % ('rtl/' if extension in ['css', 'min.css'] and self.user_direction == 'rtl' else ''),
|
||||
name=self.name,
|
||||
sep='',
|
||||
extension='.%s' % extension
|
||||
to_clean_pattern = self.get_asset_url(
|
||||
unique=ANY_UNIQUE,
|
||||
extension=extension,
|
||||
)
|
||||
|
||||
domain = [
|
||||
('url', '=like', url),
|
||||
'!', ('url', '=like', self.get_asset_url(unique=self.version))
|
||||
('url', '=like', to_clean_pattern),
|
||||
('url', '!=', keep_url),
|
||||
('public', '=', True),
|
||||
]
|
||||
|
||||
attachments = ira.sudo().search(domain)
|
||||
# avoid to invalidate cache if it's already empty (mainly useful for test)
|
||||
|
||||
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)
|
||||
# force bundle invalidation on other workers
|
||||
self.env['ir.qweb'].clear_caches()
|
||||
# clear_cache was removed
|
||||
|
||||
return True
|
||||
|
||||
|
|
@ -296,21 +212,17 @@ class AssetsBundle(object):
|
|||
by file name and only return the one with the max id for each group.
|
||||
|
||||
: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),
|
||||
else: the url contains a version equal to that of the self.version
|
||||
=> web/assets/%-self.version/name.extension.
|
||||
else: the url contains a version equal to that of the self.get_version(type)
|
||||
=> 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(
|
||||
unique=unique,
|
||||
extra='%s' % ('rtl/' if extension in ['css', 'min.css'] and self.user_direction == 'rtl' else ''),
|
||||
name=self.name,
|
||||
sep='',
|
||||
extension='.%s' % extension
|
||||
extension=extension,
|
||||
)
|
||||
self.env.cr.execute("""
|
||||
query = """
|
||||
SELECT max(id)
|
||||
FROM ir_attachment
|
||||
WHERE create_uid = %s
|
||||
|
|
@ -320,22 +232,37 @@ class AssetsBundle(object):
|
|||
AND public = true
|
||||
GROUP 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
|
||||
during a transaction, added to the ormcache but the transaction
|
||||
is rolled back, leading to 404 when getting the attachments.
|
||||
This postrollback hook will help fix this issue by clearing the
|
||||
cache if it is not committed.
|
||||
"""
|
||||
self.env.cr.postrollback.add(self.env.registry._Registry__cache.clear)
|
||||
self.env.cr.execute(query, [SUPERUSER_ID, url_pattern])
|
||||
|
||||
attachment_id = [r[0] for r in self.env.cr.fetchall()]
|
||||
if not attachment_id and not ignore_version:
|
||||
fallback_url_pattern = self.get_asset_url(
|
||||
unique=unique,
|
||||
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):
|
||||
"""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/javascript'
|
||||
)
|
||||
unique = self.get_version('css' if self.is_css(extension) else 'js')
|
||||
url = self.get_asset_url(
|
||||
unique=unique,
|
||||
extension=extension,
|
||||
)
|
||||
values = {
|
||||
'name': fname,
|
||||
'mimetype': mimetype,
|
||||
|
|
@ -368,26 +300,13 @@ class AssetsBundle(object):
|
|||
'type': 'binary',
|
||||
'public': True,
|
||||
'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,
|
||||
}
|
||||
attachment.write(values)
|
||||
attachment = ira.with_user(SUPERUSER_ID).create(values)
|
||||
|
||||
if self.env.context.get('commit_assetsbundle') is True:
|
||||
self.env.cr.commit()
|
||||
_logger.info('Generating a new asset bundle attachment %s (id:%s)', attachment.url, attachment.id)
|
||||
|
||||
self.clean_attachments(extension)
|
||||
self._clean_attachments(extension, url)
|
||||
|
||||
# For end-user assets (common and backend), send a message on the bus
|
||||
# to invite the user to refresh their browser
|
||||
|
|
@ -395,11 +314,12 @@ class AssetsBundle(object):
|
|||
self.env['bus.bus']._sendone('broadcast', 'bundle_changed', {
|
||||
'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
|
||||
|
||||
def js(self, is_minified=True):
|
||||
def js(self):
|
||||
is_minified = not self.is_debug_assets
|
||||
extension = 'min.js' if is_minified else 'js'
|
||||
js_attachment = self.get_attachments(extension)
|
||||
|
||||
|
|
@ -417,11 +337,10 @@ class AssetsBundle(object):
|
|||
* Templates *
|
||||
*******************************************/
|
||||
|
||||
odoo.define('{self.name}.bundle.xml', function(require){{
|
||||
odoo.define('{self.name}.bundle.xml', ['@web/core/registry'], function(require){{
|
||||
'use strict';
|
||||
const {{ loadXML }} = require('@web/core/assets');
|
||||
const templates = `{templates}`;
|
||||
return loadXML(templates);
|
||||
const {{ registry }} = require('@web/core/registry');
|
||||
registry.category(`xml_templates`).add(`{self.name}`, `{templates}`);
|
||||
}});""")
|
||||
|
||||
if is_minified:
|
||||
|
|
@ -443,7 +362,7 @@ class AssetsBundle(object):
|
|||
or self.save_attachment('js.map', '')
|
||||
generator = SourceMapGenerator(
|
||||
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 = []
|
||||
|
|
@ -535,7 +454,7 @@ class AssetsBundle(object):
|
|||
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))
|
||||
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
|
||||
# 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.
|
||||
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'
|
||||
attachments = self.get_attachments(extension)
|
||||
if not attachments:
|
||||
# get css content
|
||||
css = self.preprocess_css()
|
||||
if self.css_errors:
|
||||
return self.get_attachments(extension, ignore_version=True)
|
||||
if attachments:
|
||||
return attachments
|
||||
|
||||
matches = []
|
||||
css = re.sub(self.rx_css_import, lambda matchobj: matches.append(matchobj.group(0)) and '', css)
|
||||
css = self.preprocess_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:
|
||||
# move up all @import rules to the top
|
||||
matches.append(css)
|
||||
css = u'\n'.join(matches)
|
||||
css_error_message {
|
||||
content: "%s";
|
||||
}
|
||||
""" % error_message
|
||||
])
|
||||
return self.save_attachment(extension, css)
|
||||
|
||||
self.save_attachment(extension, css)
|
||||
attachments = self.get_attachments(extension)
|
||||
else:
|
||||
return self.css_with_sourcemap(u'\n'.join(matches))
|
||||
return attachments
|
||||
matches = []
|
||||
css = re.sub(self.rx_css_import, lambda matchobj: matches.append(matchobj.group(0)) and '', css)
|
||||
|
||||
if is_minified:
|
||||
# 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):
|
||||
"""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') \
|
||||
or self.save_attachment('css.map', '')
|
||||
debug_asset_url = self.get_debug_asset_url(name=self.name,
|
||||
extra='rtl/' if self.user_direction == 'rtl' else '')
|
||||
debug_asset_url = self.get_asset_url(unique='debug')
|
||||
generator = SourceMapGenerator(
|
||||
source_root="/".join(
|
||||
[".." for i in range(0, len(debug_asset_url.split("/")) - 2)]
|
||||
|
|
@ -660,7 +601,7 @@ class AssetsBundle(object):
|
|||
content_bundle_list.append(content)
|
||||
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)
|
||||
|
||||
generator._file = css_attachment.url
|
||||
|
|
@ -670,111 +611,6 @@ class AssetsBundle(object):
|
|||
|
||||
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', '
')
|
||||
|
||||
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):
|
||||
"""
|
||||
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)
|
||||
|
||||
# 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))]
|
||||
compiled += '\n'.join([asset.get_source() for asset in stylesheet_assets])
|
||||
compiled = self.run_rtlcss(compiled)
|
||||
|
|
@ -861,7 +697,7 @@ class AssetsBundle(object):
|
|||
except IOError:
|
||||
rtlcss = 'rtlcss'
|
||||
|
||||
cmd = [rtlcss, '-c', get_resource_path("base", "data/rtlcss.json"), '-']
|
||||
cmd = [rtlcss, '-c', file_path("base/data/rtlcss.json"), '-']
|
||||
|
||||
try:
|
||||
rtlcss = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
||||
|
|
@ -915,18 +751,17 @@ class AssetsBundle(object):
|
|||
|
||||
|
||||
class WebAsset(object):
|
||||
html_url_format = '%s'
|
||||
_content = None
|
||||
_filename = None
|
||||
_ir_attach = 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.inline = inline
|
||||
self._filename = filename
|
||||
self.url = url
|
||||
self.html_url_args = url
|
||||
self._last_modified = last_modified
|
||||
if not inline and not url:
|
||||
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())
|
||||
return self._id
|
||||
|
||||
@func.lazy_property
|
||||
def unique_descriptor(self):
|
||||
return f'{self.url or self.inline},{self.last_modified}'
|
||||
|
||||
@func.lazy_property
|
||||
def name(self):
|
||||
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):
|
||||
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:
|
||||
# Test url against ir.attachments
|
||||
self._ir_attach = self.bundle.env['ir.attachment'].sudo()._get_serve_attachment(self.url)
|
||||
|
|
@ -956,20 +787,20 @@ class WebAsset(object):
|
|||
except ValueError:
|
||||
raise AssetNotFound("Could not find %s" % self.name)
|
||||
|
||||
def to_node(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@func.lazy_property
|
||||
@property
|
||||
def last_modified(self):
|
||||
try:
|
||||
self.stat()
|
||||
if self._filename:
|
||||
return datetime.fromtimestamp(os.path.getmtime(self._filename))
|
||||
if self._last_modified is None:
|
||||
try:
|
||||
self.stat()
|
||||
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:
|
||||
return self._ir_attach['__last_update']
|
||||
except Exception:
|
||||
pass
|
||||
return datetime(1970, 1, 1)
|
||||
self._last_modified = self._ir_attach.write_date.timestamp()
|
||||
if not self._last_modified:
|
||||
self._last_modified = -1
|
||||
return self._last_modified
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
|
|
@ -1004,11 +835,15 @@ class WebAsset(object):
|
|||
|
||||
class JavascriptAsset(WebAsset):
|
||||
|
||||
def __init__(self, bundle, inline=None, url=None, filename=None):
|
||||
super().__init__(bundle, inline, url, filename)
|
||||
def __init__(self, bundle, **kwargs):
|
||||
super().__init__(bundle, **kwargs)
|
||||
self._is_transpiled = None
|
||||
self._converted_content = None
|
||||
|
||||
@property
|
||||
def bundle_version(self):
|
||||
return self.bundle.get_version('js')
|
||||
|
||||
@property
|
||||
def is_transpiled(self):
|
||||
if self._is_transpiled is None:
|
||||
|
|
@ -1033,21 +868,6 @@ class JavascriptAsset(WebAsset):
|
|||
except AssetError as 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):
|
||||
if minimal:
|
||||
|
|
@ -1078,24 +898,20 @@ class XMLAsset(WebAsset):
|
|||
try:
|
||||
content = super()._fetch_content()
|
||||
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)
|
||||
root = etree.parse(io.BytesIO(content.encode('utf-8')), parser=parser).getroot()
|
||||
parser = etree.XMLParser(ns_clean=True, remove_comments=True, resolve_entities=False)
|
||||
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'):
|
||||
return ''.join(etree.tostring(el, encoding='unicode') for el in root)
|
||||
return etree.tostring(root, encoding='unicode')
|
||||
|
||||
def to_node(self):
|
||||
attributes = {
|
||||
'async': 'async',
|
||||
'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)
|
||||
@property
|
||||
def bundle_version(self):
|
||||
return self.bundle.get_version('js')
|
||||
|
||||
def with_header(self, content=None):
|
||||
if content is None:
|
||||
|
|
@ -1128,21 +944,18 @@ class StylesheetAsset(WebAsset):
|
|||
rx_sourceMap = re.compile(r'(/\*# sourceMappingURL=.*)', re.U)
|
||||
rx_charset = re.compile(r'(@charset "[^"]+";)', re.U)
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
self.media = kw.pop('media', None)
|
||||
self.direction = kw.pop('direction', None)
|
||||
def __init__(self, *args, rtl=False, **kw):
|
||||
self.rtl = rtl
|
||||
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
|
||||
def content(self):
|
||||
content = super().content
|
||||
if self.media:
|
||||
content = '@media %s { %s }' % (self.media, content)
|
||||
return content
|
||||
def bundle_version(self):
|
||||
return self.bundle.get_version('css')
|
||||
|
||||
@func.lazy_property
|
||||
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):
|
||||
try:
|
||||
|
|
@ -1184,35 +997,10 @@ class StylesheetAsset(WebAsset):
|
|||
content = re.sub(r' *([{}]) *', r'\1', 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):
|
||||
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):
|
||||
raise NotImplementedError
|
||||
|
||||
|
|
@ -1271,7 +1059,7 @@ class SassStylesheetAsset(PreprocessedCSS):
|
|||
class ScssStylesheetAsset(PreprocessedCSS):
|
||||
@property
|
||||
def bootstrap_path(self):
|
||||
return get_resource_path('web', 'static', 'lib', 'bootstrap', 'scss')
|
||||
return file_path('web/static/lib/bootstrap/scss')
|
||||
|
||||
precision = 8
|
||||
output_style = 'expanded'
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue