Coverage for adhoc-cicd-odoo-odoo / odoo / orm / models_transient.py: 44%

35 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-09 18:22 +0000

1import datetime 

2 

3from odoo.tools import SQL, config, lazy_classproperty 

4from odoo.tools.constants import GC_UNLINK_LIMIT 

5 

6from . import decorators as api 

7from .models import Model 

8 

9 

10class TransientModel(Model): 

11 """ Model super-class for transient records, meant to be temporarily 

12 persistent, and regularly vacuum-cleaned. 

13 

14 A TransientModel has a simplified access rights management, all users can 

15 create new records, and may only access the records they created. The 

16 superuser has unrestricted access to all TransientModel records. 

17 """ 

18 _auto: bool = True # automatically create database backend 

19 _register: bool = False # not visible in ORM registry, meant to be python-inherited only 

20 _abstract = False # not abstract 

21 _transient = True # transient 

22 

23 # default values for _transient_vacuum() 

24 _transient_max_count = lazy_classproperty(lambda _: int(config.get('osv_memory_count_limit'))) 

25 "maximum number of transient records, unlimited if ``0``" 

26 _transient_max_hours = lazy_classproperty(lambda _: float(config.get('transient_age_limit'))) 

27 "maximum idle lifetime (in hours), unlimited if ``0``" 

28 

29 @api.autovacuum 

30 def _transient_vacuum(self): 

31 """Clean the transient records. 

32 

33 This unlinks old records from the transient model tables whenever the 

34 :attr:`_transient_max_count` or :attr:`_transient_max_hours` conditions 

35 (if any) are reached. 

36 

37 Actual cleaning will happen only once every 5 minutes. This means this 

38 method can be called frequently (e.g. whenever a new record is created). 

39 

40 Example with both max_hours and max_count active: 

41 

42 Suppose max_hours = 0.2 (aka 12 minutes), max_count = 20, there are 

43 55 rows in the table, 10 created/changed in the last 5 minutes, an 

44 additional 12 created/changed between 5 and 10 minutes ago, the rest 

45 created/changed more than 12 minutes ago. 

46 

47 - age based vacuum will leave the 22 rows created/changed in the last 12 

48 minutes 

49 - count based vacuum will wipe out another 12 rows. Not just 2, 

50 otherwise each addition would immediately cause the maximum to be 

51 reached again. 

52 - the 10 rows that have been created/changed the last 5 minutes will NOT 

53 be deleted 

54 """ 

55 has_remaining = False 

56 if self._transient_max_hours: 

57 # Age-based expiration 

58 has_remaining |= self._transient_clean_rows_older_than(self._transient_max_hours * 60 * 60) 

59 

60 if self._transient_max_count: 

61 # Count-based expiration 

62 has_remaining |= self._transient_clean_old_rows(self._transient_max_count) 

63 # This method is shared by all transient models therefore, 

64 # return the model name to be logged and if whether there are more rows to process 

65 return self._name, has_remaining 

66 

67 def _transient_clean_old_rows(self, max_count: int) -> bool: 

68 # Check how many rows we have in the table 

69 self._cr.execute(SQL("SELECT count(*) FROM %s", SQL.identifier(self._table))) 

70 [count] = self._cr.fetchone() 

71 if count > max_count: 

72 return self._transient_clean_rows_older_than(300) 

73 return False 

74 

75 def _transient_clean_rows_older_than(self, seconds: int) -> bool: 

76 # Never delete rows used in last 5 minutes 

77 seconds = max(seconds, 300) 

78 now = self.env.cr.now() 

79 domain = [('write_date', '<', now - datetime.timedelta(seconds=seconds))] 

80 records = self.sudo().search(domain, limit=GC_UNLINK_LIMIT) 

81 records.unlink() 

82 return len(records) == GC_UNLINK_LIMIT