# HG changeset patch # User Cédric Krier <ced@b2ck.com> # Date 1678209025 -3600 # Tue Mar 07 18:10:25 2023 +0100 # Branch 6.6 # Node ID a18e8ac55bc051e50c489041d5eadfe832b2d6fe # Parent dec3259fb5ec59e896ff32080276a56d88ede4bd Add trailing / to URLs of DPD shipping services Closes #12098 (grafted from a6b41e6401ec070f5cdf97115bfd2c22c3dccdac) diff --git a/modules/stock_package_shipping_dpd/configuration.py b/modules/stock_package_shipping_dpd/configuration.py --- a/modules/stock_package_shipping_dpd/configuration.py +++ b/modules/stock_package_shipping_dpd/configuration.py @@ -12,8 +12,8 @@ 'production': 'https://public-ws.dpd.com/services/', } -LOGIN_SERVICE = 'LoginService/V2_0?wsdl' -SHIPMENT_SERVICE = 'ShipmentService/V3_2?wsdl' +LOGIN_SERVICE = 'LoginService/V2_0/?wsdl' +SHIPMENT_SERVICE = 'ShipmentService/V3_2/?wsdl' TIMEOUT = config.get( 'stock_package_shipping_dpd', 'requests_timeout', default=300) # HG changeset patch # User Sergi Almacellas Abellana <sergi@koolpi.com> # Date 1678365432 -3600 # Thu Mar 09 13:37:12 2023 +0100 # Branch 6.6 # Node ID 70609fc9c0a74a93460655605519f459e13617cf # Parent a18e8ac55bc051e50c489041d5eadfe832b2d6fe Use inventory company to complete lines Closes #12127 (grafted from 5282d0322326c6abe1b98cb1dee1565064257e4f) diff --git a/modules/stock/inventory.py b/modules/stock/inventory.py --- a/modules/stock/inventory.py +++ b/modules/stock/inventory.py @@ -256,7 +256,9 @@ if inventory.state == 'done': continue # Compute product quantities - with Transaction().set_context(stock_date_end=inventory.date): + with Transaction().set_context( + company=inventory.company.id, + stock_date_end=inventory.date): if fill: pbl = Product.products_by_location( [inventory.location.id], # HG changeset patch # User Cédric Krier <ced@b2ck.com> # Date 1678449180 -3600 # Fri Mar 10 12:53:00 2023 +0100 # Branch 6.6 # Node ID 32fc398251b01a46fde9f6d995dd3514e100996e # Parent 70609fc9c0a74a93460655605519f459e13617cf Test if anchor has href attribute before converting it It is valid HTML to have anchor without href attribute. Closes #12134 (grafted from f3a2d9b2258d6004fffc5431136bd36b010b7f9b) diff --git a/modules/marketing_automation/marketing_automation.py b/modules/marketing_automation/marketing_automation.py --- a/modules/marketing_automation/marketing_automation.py +++ b/modules/marketing_automation/marketing_automation.py @@ -567,7 +567,7 @@ for kind, data, pos in stream: if kind is START: tag, attrs = data - if tag == 'a': + if tag == 'a' and attrs.get('href'): href = attrs.get('href') attrs -= 'href' if href.startswith('unsubscribe'): diff --git a/modules/marketing_email/marketing.py b/modules/marketing_email/marketing.py --- a/modules/marketing_email/marketing.py +++ b/modules/marketing_email/marketing.py @@ -451,7 +451,7 @@ for kind, data, pos in stream: if kind is START: tag, attrs = data - if tag == 'a': + if tag == 'a' and attrs.get('href'): href = attrs.get('href') attrs -= 'href' href = short(href, str(message)) # HG changeset patch # User Cédric Krier <ced@b2ck.com> # Date 1678743883 -3600 # Mon Mar 13 22:44:43 2023 +0100 # Branch 6.6 # Node ID f861f5087fd0bd2b57a480ae8b044853e8a7400f # Parent 32fc398251b01a46fde9f6d995dd3514e100996e Ensure price ratio is Decimal for product kit When the ratio is computed based on number of components instead of price list. Closes #12135 (grafted from de0b876c414875b7f317b3531859d4b7ac25e8d5) diff --git a/modules/product_kit/common.py b/modules/product_kit/common.py --- a/modules/product_kit/common.py +++ b/modules/product_kit/common.py @@ -311,7 +311,7 @@ if sum_: ratio = list_prices[component] / sum_ else: - ratio = 1 / len(components) + ratio = Decimal(1) / len(components) if component.fixed: ratio /= Decimal(str(component.quantity / quantity)) else: # HG changeset patch # User Cédric Krier <ced@b2ck.com> # Date 1678892950 -3600 # Wed Mar 15 16:09:10 2023 +0100 # Branch 6.6 # Node ID 943b19e831957035d57f666aa2bfe12751059917 # Parent f861f5087fd0bd2b57a480ae8b044853e8a7400f Do not copy password hash nor reset from user (grafted from c94f99afeadbc10e483ed3d34d707635f77ac945) diff --git a/modules/web_user/user.py b/modules/web_user/user.py --- a/modules/web_user/user.py +++ b/modules/web_user/user.py @@ -162,6 +162,13 @@ cls.save(users) @classmethod + def copy(cls, users, default=None): + default = default.copy() if default is not None else {} + default['password_hash'] = None + default['reset_password_token'] = None + return super().copy(users, default=default) + + @classmethod def create(cls, vlist): users = super(User, cls).create(vlist) cls._format_email(users) diff --git a/trytond/trytond/res/user.py b/trytond/trytond/res/user.py --- a/trytond/trytond/res/user.py +++ b/trytond/trytond/res/user.py @@ -439,7 +439,8 @@ default = {} default = default.copy() - default['password'] = '' + default['password_hash'] = None + default['password_reset'] = None default.setdefault('warnings') default.setdefault('applications') # HG changeset patch # User Cédric Krier <ced@b2ck.com> # Date 1678870382 -3600 # Wed Mar 15 09:53:02 2023 +0100 # Branch 6.6 # Node ID 3f2e658af5e4edd0a1e557746f79ee26594aab63 # Parent 943b19e831957035d57f666aa2bfe12751059917 Reset next number of invoice sequences when renew fiscal year (grafted from fcf3710fa6cf50cc25de17b6e65acdc7f96a3510) diff --git a/modules/account_invoice/account.py b/modules/account_invoice/account.py --- a/modules/account_invoice/account.py +++ b/modules/account_invoice/account.py @@ -399,11 +399,13 @@ sequence = getattr(invoice_sequence, field, None) sequences[sequence.id] = sequence copies = Sequence.copy(list(sequences.values()), default={ - 'next_number': 1, 'name': lambda data: data['name'].replace( self.start.previous_fiscalyear.name, self.start.name) }) + Sequence.write(copies, { + 'number_next': Sequence.default_number_next(), + }) mapping = {} for previous_id, new_sequence in zip(sequences.keys(), copies): # HG changeset patch # User Cédric Krier <ced@b2ck.com> # Date 1678200233 -3600 # Tue Mar 07 15:43:53 2023 +0100 # Branch 6.6 # Node ID c249ee8605b14801e80bda4e1ce1762ce4475cc9 # Parent 3f2e658af5e4edd0a1e557746f79ee26594aab63 Keep all transaction caches still referenced The LRUDict is replaced by a WeakValueDictionary to keep all used transaction caches such as they can be cleared by ModelStorage. But we still keep the latest cache used so they are not removed from the WeakValueDictionary too soon when such cache has been created to be pre-filled like in ModelSQL.search. Closes #12119 (grafted from 72f5f8ed9b1a9be670ba9c3dd29532f328373e7d) diff --git a/trytond/trytond/transaction.py b/trytond/trytond/transaction.py --- a/trytond/trytond/transaction.py +++ b/trytond/trytond/transaction.py @@ -2,8 +2,9 @@ # this repository contains the full copyright notices and license terms. import logging import time -from collections import defaultdict +from collections import defaultdict, deque from threading import local +from weakref import WeakValueDictionary from sql import Flavor @@ -67,19 +68,11 @@ started_at = None def __new__(cls, new=False): - from trytond.cache import LRUDict - from trytond.pool import Pool transactions = cls._local.transactions if new or not transactions: instance = super(Transaction, cls).__new__(cls) - instance.cache = LRUDict( - _cache_transaction, - lambda: LRUDict( - _cache_model, - lambda name: LRUDict( - record_cache_size(instance), - Pool().get(name)._record), - default_factory_with_key=True)) + instance.cache = WeakValueDictionary() + instance._cache_deque = deque(maxlen=_cache_transaction) instance._atexit = [] transactions.append(instance) else: @@ -98,10 +91,21 @@ return self._local.tasks def get_cache(self): + from trytond.cache import LRUDict + from trytond.pool import Pool keys = tuple(((key, self.context[key]) for key in sorted(self.cache_keys) if key in self.context)) - return self.cache[(self.user, keys)] + cache = self.cache.setdefault( + (self.user, keys), LRUDict( + _cache_model, + lambda name: LRUDict( + record_cache_size(self), + Pool().get(name)._record), + default_factory_with_key=True)) + # Keep last used cache references to allow to pre-fill them + self._cache_deque.append(cache) + return cache def start(self, database_name, user, readonly=False, context=None, close=False, autocommit=False): # HG changeset patch # User Cédric Krier <ced@b2ck.com> # Date 1678203238 -3600 # Tue Mar 07 16:33:58 2023 +0100 # Branch 6.6 # Node ID f22d879b713323fc9d17d7c420478df035c8c0d2 # Parent c249ee8605b14801e80bda4e1ce1762ce4475cc9 Remove password hash also from the transaction cache Closes #12120 (grafted from 7b5157206d328bfe020fabece59fb7747dd2dd9d) diff --git a/trytond/trytond/res/user.py b/trytond/trytond/res/user.py --- a/trytond/trytond/res/user.py +++ b/trytond/trytond/res/user.py @@ -364,12 +364,29 @@ @classmethod def read(cls, ids, fields_names): result = super(User, cls).read(ids, fields_names) - if not fields_names or 'password_hash' in fields_names: - for values in result: + cache = Transaction().get_cache()[cls.__name__] + for values in result: + if 'password_hash' in values: values['password_hash'] = None + if values['id'] in cache: + cache[values['id']]['password_hash'] = None return result @classmethod + def search( + cls, domain, offset=0, limit=None, order=None, count=False, + query=False): + users = super().search( + domain, offset=offset, limit=limit, order=order, count=count, + query=query) + cache = Transaction().get_cache()[cls.__name__] + if not count and not query: + for user in users: + if user.id in cache: + cache[user.id]['password_hash'] = None + return users + + @classmethod def create(cls, vlist): vlist = [cls._convert_vals(vals) for vals in vlist] return super(User, cls).create(vlist) # HG changeset patch # User Cédric Krier <ced@b2ck.com> # Date 1678317666 -3600 # Thu Mar 09 00:21:06 2023 +0100 # Branch 6.6 # Node ID b07f0b7fd83bd52bed960bd86ce5e1daaac1bccb # Parent f22d879b713323fc9d17d7c420478df035c8c0d2 Call record saved after saving the current record of the screen Since 403bc7a95b0a record_saved has been implemented but it is not called by the Screen when saving record. Closes #12117, #12137 (grafted from 4cca7cca2fc1f4a8cced8951dda60dde35e36a7b) diff --git a/sao/src/screen.js b/sao/src/screen.js --- a/sao/src/screen.js +++ b/sao/src/screen.js @@ -1600,7 +1600,9 @@ } const display = () => { // Return the original promise to keep succeed/rejected state - return this.display().then(() => prm, () => prm); + return this.display() + .always(() => this.record_saved()) + .then(() => prm, () => prm); }; return prm.then(current_record => { if (path && current_record && current_record.id) { # HG changeset patch # User Cédric Krier <ced@b2ck.com> # Date 1678983214 -3600 # Thu Mar 16 17:13:34 2023 +0100 # Branch 6.6 # Node ID a3f4a38095c50536bb9ad099614276d1bd02d77a # Parent b07f0b7fd83bd52bed960bd86ce5e1daaac1bccb Do not create marketing parameter when context value is an empty string Closes #12145 (grafted from c3ad06a9ac25f378aebaf93489ad2a68302f77b5) diff --git a/modules/marketing_campaign/marketing.py b/modules/marketing_campaign/marketing.py --- a/modules/marketing_campaign/marketing.py +++ b/modules/marketing_campaign/marketing.py @@ -108,7 +108,9 @@ default = super().default_get( fields_names, with_rec_name=with_rec_name) for fname in cls.marketing_campaign_fields(): - if isinstance(context.get(fname), str) and not default.get(fname): + if (isinstance(context.get(fname), str) + and context[fname] + and not default.get(fname)): field = getattr(cls, fname) Target = field.get_target() target = Target.from_name( # HG changeset patch # User Sergi Almacellas Abellana <sergi@koolpi.com> # Date 1679644569 -3600 # Fri Mar 24 08:56:09 2023 +0100 # Branch 6.6 # Node ID 9a6bb51bcefbe62fbfb51c01db1bbc3eaca95220 # Parent a3f4a38095c50536bb9ad099614276d1bd02d77a Do not copy templates relation when duplicating a category Closes #12158 (grafted from 17f94a972a316d3ea1aebdeb1615b1fa16883220) diff --git a/modules/product/category.py b/modules/product/category.py --- a/modules/product/category.py +++ b/modules/product/category.py @@ -36,3 +36,9 @@ 'pyson_domain': PYSONEncoder().encode( [('id', '=', categories[0].id)]), } + + @classmethod + def copy(cls, categories, default=None): + default = default.copy() if default is not None else {} + default.setdefault('templates') + return super().copy(categories, default=default)