Compare commits
1007 Commits
15.0
...
cx_web_ref
| Author | SHA1 | Date | |
|---|---|---|---|
| 3e756c774f | |||
| e8fe926dbb | |||
| b2a168bd06 | |||
| 4628821451 | |||
| ab108c0437 | |||
| 5d9fe7b1f3 | |||
| 1b3bf2d301 | |||
| 017d7883d3 | |||
| 00439eac6e | |||
| d5b31de87e | |||
| f9a6b56c81 | |||
| 123239031f | |||
| 7f65dc9a92 | |||
| 25c345f665 | |||
| 2c2058e07b | |||
| f628de0c15 | |||
| 998d97da45 | |||
| 7a6f12082b | |||
|
|
9bb80002c8 | ||
| 2cf3b5185d | |||
| 9214b650ae | |||
| e6027c710b | |||
| 7a40f423d4 | |||
| 9966aa4ffa | |||
| 7c90e7bee2 | |||
| be3a699471 | |||
| a68f5ed5da | |||
| 3a4d546c10 | |||
| 386893e751 | |||
| e8cd94cc98 | |||
| bbf383a59f | |||
| eb41cf2557 | |||
| 7d71cd87fe | |||
| 886affc442 | |||
| 236c25028c | |||
| ca0996089d | |||
| 326bbe4eb1 | |||
| bd2de68a13 | |||
| 241485ada6 | |||
| 93925e7edb | |||
| 8be03b6213 | |||
| f8be9708f2 | |||
| 43f1d3a460 | |||
| 95ab9b7b07 | |||
| c4dec5820f | |||
| cf8020a374 | |||
| 8b2b00309b | |||
| 43c386dd0f | |||
| 94e68bf101 | |||
| c6fcbd4e88 | |||
| 95ec10c4cd | |||
| 8cde5f5b85 | |||
| 92daedbcfe | |||
| 59af83f001 | |||
| 68ca5feaec | |||
| d379066f8d | |||
| 50f45a25f6 | |||
| 4f1b44f859 | |||
| 63cb075ebe | |||
| 3bc89980fb | |||
| a30f10f463 | |||
| a3f387f59d | |||
| 0496b94742 | |||
| eeea0bde1d | |||
| 19a63eac0c | |||
| ff42b73982 | |||
| ac9207b0c6 | |||
| 7dadc5938a | |||
| 88fb907a24 | |||
| a349d97184 | |||
| 2eee80609f | |||
| fcb747f2c9 | |||
| 826194b88f | |||
| 303d179c1a | |||
| b309b6f244 | |||
| ee0ef6b69d | |||
| 121fcd2639 | |||
| 5cecb3364e | |||
| 1e2909808a | |||
| b8658d0fed | |||
| ea586f0019 | |||
| 4219a55576 | |||
| 2d6197c181 | |||
| 049d8dc461 | |||
| 8f7afa25fc | |||
| 7c0c8cf62a | |||
| e0c9e3817f | |||
| 95894e0965 | |||
| 0fd426db38 | |||
| 7f38305cda | |||
| 9eb03938ba | |||
| 38b16543da | |||
| 3a6e036b1d | |||
| c28baccca8 | |||
| 2352fdfaab | |||
| fd93d95ef0 | |||
| 1ad508d029 | |||
| 80e2953742 | |||
| 8f2214eb37 | |||
| 105434392b | |||
| d77c0ec323 | |||
| ebc9c2da81 | |||
| b706079a96 | |||
| 1a3fb88ff0 | |||
| fe73ec564d | |||
| 38becb2347 | |||
| 9cc237dfcd | |||
| 59a0a18068 | |||
| 3cdcbd70c3 | |||
| 36f48cb3ef | |||
| 6e40ceba46 | |||
| 9ed9312f52 | |||
| 3bf88a3db2 | |||
| cd835868a4 | |||
| 3adbefa88a | |||
| 6192b3aff5 | |||
| b504a42afc | |||
| 60532fda2e | |||
| 4604622231 | |||
| f4e43a2f84 | |||
| c5c3887d97 | |||
| 8ce3697a6e | |||
| bbe37b34d9 | |||
| ae8c208c59 | |||
| 3811a9d7cd | |||
| 3eed4a9224 | |||
| 68b713a69a | |||
| af1be38c68 | |||
| 3780eaf1f2 | |||
| 1f23472908 | |||
| 5e71e0a98a | |||
| 5e665079f3 | |||
| b67d86ef4b | |||
| cf83815785 | |||
| 255a06a789 | |||
| 3153e06601 | |||
| 7e4c9dcd99 | |||
| 8e92de0c4e | |||
| eb58ac434c | |||
| 5aed68117b | |||
| b6c3a855a4 | |||
| cd4b7cd2e6 | |||
| cb1e060ebb | |||
| ccc9ef0644 | |||
| 03b1d2bd59 | |||
| 565ef8f0cc | |||
| ae52efe220 | |||
| 09576d37b4 | |||
| 3b556e385d | |||
| d24677fc8e | |||
| bc9c9bf125 | |||
| 953db1c18e | |||
| bb6408e1bb | |||
| f5ee80a075 | |||
| e2a1ef97cf | |||
| f7aa151993 | |||
| c8461e648d | |||
| 78f57a52e8 | |||
| 645bbb9e1f | |||
| 1c5b8f78e8 | |||
| b06be61868 | |||
| 22635fd1db | |||
| 502f3c276f | |||
| a29e7d3701 | |||
| 90a6de998c | |||
| 309834e218 | |||
| 36548b07b3 | |||
| a647db524a | |||
| 96392d7cab | |||
| 3c73cf7286 | |||
| 5128d56863 | |||
| ec366ca548 | |||
| eaaf033cd5 | |||
| 899eefb2b1 | |||
| 35c152c073 | |||
| 4db61b7221 | |||
| 56faad93d6 | |||
| b02021eeab | |||
| 0f8d4c52be | |||
| f6b23b2697 | |||
| 9c162c8c40 | |||
| 9ee9d2dc06 | |||
| f88873226d | |||
| e6ef101e11 | |||
| ace82b83f0 | |||
| c3b5ebd7bc | |||
| f52001bffa | |||
| 8289f0ec93 | |||
| 4c85b5da81 | |||
| 7bddb74ba7 | |||
| b088c69c2b | |||
| ffc66cfaa1 | |||
| 50705d8a20 | |||
| eb85e1f034 | |||
| c104fc8c43 | |||
| 12aaeebb69 | |||
| b8accd5199 | |||
| 36e718ae36 | |||
| fc67cb263f | |||
| d6bec900fe | |||
| db967ff6e3 | |||
| 9d6d07b2e1 | |||
| 1444a3064e | |||
| 0a2e334aee | |||
| 15d21d3ab5 | |||
| a4f565087c | |||
| 6a11883c7c | |||
| 1202892973 | |||
| d3e1cc28bc | |||
| 151246c614 | |||
| 5049ba6907 | |||
| f8b593bec4 | |||
| 682e48bb2b | |||
| c4ddcb8e92 | |||
| d992b45cdd | |||
| f89637d6e8 | |||
| a493701b6e | |||
| 77b6a4b2fc | |||
| 9d595b2565 | |||
| 812b64b18d | |||
| a6d4d64192 | |||
| 0f52520ee3 | |||
| 8a996f5083 | |||
| b4e0d93c28 | |||
| f54217e713 | |||
| ebb1399951 | |||
| f9d33713ab | |||
| 4af3a1e647 | |||
| af44da007b | |||
| 1d5a2ceed0 | |||
| 4f9d558cf4 | |||
| 8bdd6064ab | |||
| 76b43e0b65 | |||
| 3b80e2101c | |||
| 42653be822 | |||
| 717ce4756a | |||
| 5ac0c18619 | |||
| 79dd86e13f | |||
| 05b6f98a82 | |||
| 098ce63b26 | |||
| 254f6312b4 | |||
| 5dc4447ff3 | |||
| d77978d0c9 | |||
| c4df067894 | |||
| c210efa9a2 | |||
| 3dbf0baeb2 | |||
| 5903af189c | |||
| 507eab7847 | |||
| 0f5b16febc | |||
| 1b5342da13 | |||
| 9d2776f6ce | |||
| 716a6d735c | |||
| 5362b526e7 | |||
| 0586c40667 | |||
| 9e48c4e0dc | |||
| 569a853b3a | |||
| 74a3f434a4 | |||
| a6af9583df | |||
| 326900b3a7 | |||
| c4dfb0a886 | |||
| a5efdebe15 | |||
| cb84ce69be | |||
| 7e5354d5ad | |||
| 521d765b35 | |||
| 7fbaaf189b | |||
| 6a9182c820 | |||
| 90f7afa720 | |||
| 90c893ae66 | |||
| 8d55a7ee70 | |||
| f6e32ece58 | |||
| 162ad41852 | |||
| 8b89083a51 | |||
| 68c5b015e0 | |||
| 61e360ae9e | |||
| 50663c7700 | |||
| 4da1291a50 | |||
| 94df75a9bf | |||
| 5ea3184c5e | |||
| 099bb0dfd5 | |||
| e318c89788 | |||
| 1174604b05 | |||
| 364f37aa5a | |||
| bbb71840c1 | |||
| c2923e01e6 | |||
| 4131ae0adb | |||
| f16e0abf03 | |||
| 07adc2628c | |||
| a682b818d3 | |||
| b2a8c2dcde | |||
| 2314a8f6c2 | |||
| f2033e0408 | |||
| 38d4a641f1 | |||
| 8ca3561c1a | |||
| bf7158a6e8 | |||
| 6a92d586a4 | |||
| 2a93942be1 | |||
| e81e6f4ff1 | |||
| 5e4cf9723b | |||
| a6fb93b231 | |||
| a3022f8332 | |||
| af0b27d762 | |||
| d2a8d537f6 | |||
| 52d65dabba | |||
| cbd499b88f | |||
| d59aa0179a | |||
| bf61f4eb4f | |||
| 97352ac44d | |||
| eb0687da36 | |||
| ceea23aaaf | |||
| 802a5a8a1c | |||
| 2d24db2b08 | |||
| bfb1edac54 | |||
| b12ac35b65 | |||
| 8b881eba5b | |||
| b2317325e2 | |||
| a8e845cb8b | |||
| 14bd70a67c | |||
| 82cfb22c03 | |||
| 50cd192fd8 | |||
| 62a0a9b1bd | |||
| 88b9f087a1 | |||
| 55b5ad56c8 | |||
| 00d5145aa0 | |||
| 6161522a04 | |||
| abaabc4cc0 | |||
| 2686884335 | |||
| f41c8d4c6f | |||
| a9496bdbd2 | |||
| 5e634580c2 | |||
| 37b4590851 | |||
| 81e565fdec | |||
| b912c85da7 | |||
| 7ef63fa72d | |||
| 627c3e9ffa | |||
| 876b3f4a7d | |||
| f323562bbd | |||
| f98711ec66 | |||
| 8db97cf776 | |||
| f38452a52a | |||
| 03d392803f | |||
| 8b40cf2659 | |||
| 86cf1847f4 | |||
| 45d7ed8b73 | |||
| 0b7b196159 | |||
| 33ad700d61 | |||
| 7f8552981b | |||
| 5cc9d2512b | |||
| 6ab789e36d | |||
| f8109af6ce | |||
| 1bfc6ac3af | |||
| 548817116a | |||
| b2f712e6b6 | |||
| 93739ddaff | |||
| 1373305638 | |||
| 70a54d0787 | |||
| 40a164b1b0 | |||
| bc966d56b0 | |||
| 61e7953827 | |||
| 192e1a9dbc | |||
| 2fe3c98c53 | |||
| 5f3cca665d | |||
| 88d5b049ff | |||
| 4614cb31fb | |||
| 44d76bc4a1 | |||
| 353350476d | |||
| 76d63a2fe8 | |||
| cce75a5895 | |||
| 2409adec5e | |||
| f75a5247b5 | |||
| 8cfa1310d1 | |||
| 086b96cc39 | |||
| 908aa7e1da | |||
| b6218e6b07 | |||
| 3d868855ca | |||
| 7e7213f67b | |||
| f5afc7d31c | |||
| 72737afb61 | |||
| b39e81512e | |||
| 62fad98a12 | |||
| 98c4aa7358 | |||
| 1a3ee1d4de | |||
| be2facb1f1 | |||
| 97b4731ca6 | |||
| 83326bcaed | |||
| 09eda63698 | |||
| d0aaff2135 | |||
| 7bd0bd0989 | |||
| 94bd98ab65 | |||
| 25fa4c853b | |||
| 4b4581ff2e | |||
| e1ea01e59e | |||
| da4d5c2494 | |||
| 3713992eb3 | |||
| e1c8337464 | |||
| 6c81ab18c8 | |||
| f0085588b9 | |||
| 8fdb330a6c | |||
| fb7a94f8ae | |||
| e71e3822e0 | |||
| e289fb9145 | |||
| bf2a8c4010 | |||
| 5cc34775f3 | |||
| 7f75afbdba | |||
| 1390ec0c15 | |||
| 4e84600e7e | |||
| c0ea49872c | |||
| 0685e4cabe | |||
| 875a590978 | |||
| 98d8aeb011 | |||
| 2f9905210a | |||
| 41d56a304a | |||
| 55da1da7f3 | |||
| a65d315cda | |||
| 16e6c57a3f | |||
| 638a2df7c6 | |||
| 9b53282444 | |||
| 89d02b353b | |||
| c968cca4de | |||
| e163b0d99b | |||
| 4426a5abb6 | |||
| 8c8e527249 | |||
| f50c1865d5 | |||
| f5257dc7ab | |||
| 14315e0035 | |||
| e2fe62b379 | |||
| 1f9bea1264 | |||
| 08f1a4d3cb | |||
| be8a0ac9c3 | |||
| a383f11d2a | |||
| 91618edeec | |||
| b64f6da230 | |||
| ba4b26b175 | |||
| 525c919b3a | |||
| d7f6de08e9 | |||
| 0d20ab7aa8 | |||
| 231d59da7f | |||
| 4fef277751 | |||
| 7032e64dcb | |||
| 15481c11c0 | |||
| 5930f771e5 | |||
| 2290c90089 | |||
| eb28ba0f78 | |||
| 7df203efe8 | |||
| 766b1dd779 | |||
| e992a8ea3f | |||
| e3863de129 | |||
| bfed6bdcf4 | |||
| f4834218ce | |||
| e3e001ac5b | |||
| afc1985114 | |||
| f0c0fa15ac | |||
| 3bb17aa74a | |||
| 3887d3afd3 | |||
| ccf1ac8e77 | |||
| df84150f14 | |||
| fd4f3c89cf | |||
| 46fa14692c | |||
| 2d26b672bf | |||
| f908dd5acd | |||
| f50135b5c6 | |||
| 2be8fc95b7 | |||
| 736d4b40e0 | |||
| 7fa0a4c79a | |||
| 8881aeb2ca | |||
| 8fa1c29c59 | |||
| 318ef00817 | |||
| 3fcdf69f56 | |||
| e2785fff55 | |||
| e5adc46d44 | |||
| 6052fcbe7e | |||
| 268e7e9b23 | |||
| 53f93fe01b | |||
| 202cb09e2b | |||
| c29df047d2 | |||
| 8dc8a3d39b | |||
| 34a4c17e9b | |||
| ccb61bd1d9 | |||
| be89574d27 | |||
| e8a12c08d8 | |||
| 16a5812db0 | |||
| 366eeab65d | |||
| ce185f92e3 | |||
| 276dccf5fd | |||
| 4fc6c80352 | |||
| 016afe19f8 | |||
| 7295ad401c | |||
| 1eed0fbda0 | |||
| da449b9852 | |||
|
|
9a499aced7 | ||
| c3f2c4f522 | |||
| a356f71745 | |||
| 0a76509365 | |||
| 9083833444 | |||
| ed9dc11c4b | |||
| 1c2d483a46 | |||
| 1f073938d7 | |||
| e12133fff0 | |||
| 61d95f53d6 | |||
| ebd9d7c16f | |||
| 455a7192d3 | |||
| 4c8ec8ffa3 | |||
| 2beca515ca | |||
| e1c3544f09 | |||
| 242fc36fd0 | |||
| 4754df89b7 | |||
| e050040bae | |||
| 30deb10c27 | |||
| f2791efa4f | |||
| acc9748aa2 | |||
| b36fe277c0 | |||
| 0b07e80ecb | |||
| b448019f93 | |||
| a108daebd0 | |||
| 2c950b5a0a | |||
| 0ded373257 | |||
| 4ad6ffce26 | |||
| 265f8092e5 | |||
| 6735056551 | |||
| a70aae6404 | |||
| f7c2027d17 | |||
| 8f9c4f4ed9 | |||
| cd4fa9df4d | |||
| b776dea93e | |||
| 2d655e9822 | |||
| f1aef07115 | |||
| ceac54f2a8 | |||
| 8cd2d73dd1 | |||
| 4c8a85091e | |||
| 1d8e306a5b | |||
| ff76148774 | |||
| 263616cf66 | |||
| 5d8563cf5e | |||
| 3a8e7bb486 | |||
| dac924d260 | |||
| 2fec9bae57 | |||
| a3a0e7f1a9 | |||
| 57e4922aa4 | |||
| 5a86e951c9 | |||
| 07a2292b98 | |||
| 498ebf5b5e | |||
| 0c0a53fb80 | |||
| f13d08283f | |||
| 6776b72adf | |||
| fc0111f9af | |||
| 58ecb0871a | |||
| 5ab36dc181 | |||
| 9eb4e7eb59 | |||
| b73d178751 | |||
| aeb5997303 | |||
| b6be8f1f51 | |||
| 6e4ec0f188 | |||
| d84a9d95e3 | |||
| e85ead856e | |||
| d640f69b64 | |||
| 3a855c6905 | |||
| e5bc9f79bc | |||
| e4a9a645a1 | |||
| 7be7fc0d06 | |||
| 639c21ee64 | |||
| 83463f08fd | |||
| d62c41a4c0 | |||
| 5bc32a7b39 | |||
| b87f9f2f0f | |||
| ecb8f48fc7 | |||
| 453e861eb1 | |||
| 9d9ba90f65 | |||
| c0ec43dc15 | |||
| 8bf0f53a1f | |||
| b9935804d8 | |||
| e3cd173cae | |||
| 0b171536bd | |||
| 4df5459abb | |||
| 99c360c739 | |||
| 238f3a123f | |||
| 7635f192bc | |||
| 4be4c95126 | |||
| c7174e1aa0 | |||
| 038f113176 | |||
| e640b7dc00 | |||
| 69ee0e8a79 | |||
| 44f9bf1d9d | |||
| 528d049505 | |||
| d38fff270d | |||
| f47e6d77df | |||
| bcdb9f7100 | |||
| 79c3511ced | |||
| 77163b4017 | |||
| 52512ff961 | |||
| 8e37d624ff | |||
| 2b845fa110 | |||
| 09c730c852 | |||
| 9ea0b5ca1e | |||
| 3f7c12c4cb | |||
| 52212a4234 | |||
| aad83a6e45 | |||
| b15249a7cc | |||
| 971c0b6bb1 | |||
| c8259d3fae | |||
| c1c1591ffd | |||
| 1ceae80007 | |||
| 711c240058 | |||
| ed906caf54 | |||
| fa316c0948 | |||
| 64da73b835 | |||
| 51d27b362c | |||
| 11bf6134ec | |||
| e6fd3d1594 | |||
| 6c440b46d0 | |||
| 6f5049ff54 | |||
| 785da91996 | |||
| a59be21d5c | |||
| dcd8d31c0b | |||
| bbff9cd347 | |||
| c5433c7b44 | |||
| 52e3ae85ef | |||
| 6ee8d34a08 | |||
| fc845bf79a | |||
| aa3ea16a1b | |||
| 5d72ad53fb | |||
| d5a16b3baa | |||
| 4300c11cb1 | |||
| 611bf9a530 | |||
| f665a077cb | |||
| 62c2411818 | |||
| 1aeff8903c | |||
| 7a0fe4f363 | |||
| 6ebe68c8fc | |||
| 075a340056 | |||
| d764da736d | |||
| b9ee75209a | |||
| 389ffcbadb | |||
| 37ec61eab2 | |||
| a901150981 | |||
| d7e623d816 | |||
| 45e7dd2c6c | |||
| 25ca66a86b | |||
| a78cbd6583 | |||
| 09283bba61 | |||
| e738070b24 | |||
| 0c67e0d798 | |||
| 9e7f379923 | |||
| 93426fbae5 | |||
| f1b9101b13 | |||
| 33d3b02763 | |||
| 2a169afa62 | |||
| a2fc7b47c1 | |||
| c717460cea | |||
| a8f57858d8 | |||
| 3ab0e79386 | |||
| 236b78c33b | |||
| 18138fd178 | |||
| fa34dcb44e | |||
| f8a7e22755 | |||
| 5e7a05b949 | |||
| 676d1fd8c1 | |||
| 9a569b9af1 | |||
| bfda22756e | |||
| 237d692cd1 | |||
| 31b57b24f5 | |||
| ff8cc52e41 | |||
| f6d8bab742 | |||
| 1bd0a301dd | |||
| d7ac3f27e0 | |||
| adf18c04a9 | |||
| 150023c203 | |||
| 21bf17da38 | |||
| 54c3d037a0 | |||
| cc7c59f4fa | |||
| 491c5fa696 | |||
| cb1c08a52d | |||
| 7c414a48de | |||
| 0d03d8d956 | |||
| ee974eb8d1 | |||
| 5e17847c10 | |||
| 753ee2e4e3 | |||
| 2c73095fd6 | |||
| c31875dc95 | |||
| 39ac32de74 | |||
| 3a6f76212d | |||
| 89c5d6195a | |||
| a6c0f93af0 | |||
| f52cdfe12d | |||
| fe91357505 | |||
| e4c578f20a | |||
| d9734e17fb | |||
| 4cc3f94635 | |||
| 8ff4dd5b3c | |||
| 423b6acfde | |||
| ccf2ec0804 | |||
| 002f0bda6f | |||
| 753aee7fc5 | |||
| c96b8b5c64 | |||
| 821e088b8e | |||
| 3ea5965a99 | |||
| 5ce2821634 | |||
| 5d4b6ee4fc | |||
| 7399e63d70 | |||
| fc27945b4e | |||
| 2b98a8ca57 | |||
| d50103ca24 | |||
| 52a3aaec81 | |||
| 48d9f3af80 | |||
| 7575ca93ad | |||
| dcf7f0daa9 | |||
| ffe3f45168 | |||
| bd17bdabf9 | |||
| 118c993e31 | |||
| 33707afaaf | |||
| 5fc79cb304 | |||
| b595872bb3 | |||
| e50e55444c | |||
| 0f68d7e8f9 | |||
| f161ba7728 | |||
| fb2479c6fe | |||
| f8f75587c3 | |||
| 3cbf6b5144 | |||
| 48cf579779 | |||
| 5c5edd2106 | |||
| 4a4b2f7206 | |||
| e84cdd1a54 | |||
| 410eaab039 | |||
| 8b8dd4758f | |||
| 16dfa61d3d | |||
| cd0dcdb06b | |||
| 0bc76ebcfb | |||
| b86383444b | |||
| e788902689 | |||
| 2d0d90ba7e | |||
| c56cba7257 | |||
| 63623d0dc3 | |||
| 1a409eab82 | |||
| 135242d577 | |||
| aec96c5193 | |||
| a68eef6627 | |||
| 0ed1ee7bb0 | |||
| ccdde704c7 | |||
| f2803833a6 | |||
| 884df448f0 | |||
| a1cba28258 | |||
| eddb6db49e | |||
| 67267c20a3 | |||
| 547da816ad | |||
| ecf57118ba | |||
| 3681255837 | |||
| f97595108f | |||
| 7bf6c0aa4b | |||
| 9fb24cad21 | |||
| b036b9f480 | |||
| 2d677619f3 | |||
| 576e6edecc | |||
| 0819ee5593 | |||
| 7ca45462cf | |||
| 0e8399ae71 | |||
| cf42f5455d | |||
| 50536e5867 | |||
| e3c584c560 | |||
| c48e67db97 | |||
| fd8c6f50f2 | |||
| e09e6074c8 | |||
| c470e9603f | |||
| faf0882b3a | |||
| e96209ad14 | |||
| 1912047142 | |||
| 8011416ceb | |||
| 2d3f527e24 | |||
| aed221fa34 | |||
| 425d46b0ef | |||
| b3d43ad682 | |||
| 65af6a8437 | |||
| ac07f2e7df | |||
| 4d1c88b43b | |||
| 465720ccdd | |||
| 21fe8ed322 | |||
| 01d440de88 | |||
| aba0ce3a6d | |||
| 906217822f | |||
| 9e81d94e5d | |||
| 092f3b1026 | |||
| 6739cac57b | |||
| 2d69ab84ce | |||
| af3a8d4dbc | |||
| 0792585612 | |||
| ee798802e6 | |||
| 3f44bf0577 | |||
| eaedae0565 | |||
| 9c068d07c9 | |||
| 8abe88b2d3 | |||
| ece70df8fe | |||
| ecc375cf63 | |||
| d89a5b2e4b | |||
| d041586baf | |||
| 49472d32a1 | |||
| aa83b5c270 | |||
| 24bd37cd38 | |||
| a4e24baeb5 | |||
| f0512fdce3 | |||
| 101b6c9b74 | |||
| 9db3f91c28 | |||
| 94de4c07da | |||
| 67eedfddc4 | |||
| 829ec15b5b | |||
| e8bc314cfb | |||
| 85328a6e96 | |||
| 3af743ed1b | |||
| b3b67e7abf | |||
| f2d4464209 | |||
| 3fa52b6f32 | |||
| 01ad3e8a8a | |||
| fc4bbe2568 | |||
| d1a407c6b8 | |||
| a210ca3e17 | |||
| 276f4a5aa4 | |||
| 56b5e117f1 | |||
| 00c5630072 | |||
| f3a4594386 | |||
| ed335e5010 | |||
| cdd5dbb5ea | |||
| 0e51ca5bce | |||
| 0f24587641 | |||
| 5b1f74e263 | |||
| 0a2171ae91 | |||
| 09573a7cfc | |||
| 686c4f2698 | |||
| 9273950afc | |||
| add6fe3994 | |||
| 581efc0235 | |||
| b1d9b4810f | |||
| a868059124 | |||
| 2ceb788c2e | |||
| 3ca93dfc54 | |||
| e8315b207d | |||
| 3090536fc7 | |||
| c5034b43ab | |||
| 6b4b7939b0 | |||
| 99be7c765a | |||
| a018275ebf | |||
| fb9777247a | |||
| 1e8239c7e2 | |||
| 81b127648b | |||
| 4c5e3f2f28 | |||
| 9fb7d3ff42 | |||
| ac3093b5dc | |||
| 975d29fa31 | |||
| 94adb5f348 | |||
| fba8cc9acd | |||
| 0fb75fa95f | |||
| 840c4ac3a9 | |||
| 16613c86a7 | |||
| 0c43591378 | |||
| 9ea6ac8d24 | |||
| 394210c99b | |||
| 0a7c8c70b0 | |||
| 50cfffdb19 | |||
| 262f2ceef5 | |||
| 5377ca3d57 | |||
| 2fc48951c8 | |||
| b5308c583c | |||
| ffc4690cf5 | |||
| aace3488c5 | |||
| 9782ca679d | |||
| 258500d3f7 | |||
| 20a3a2d33d | |||
| 09b6d24303 | |||
| 244569004b | |||
| 773928cf47 | |||
| 0b60b46a40 | |||
| 4a4797e49d | |||
| a26a507d31 | |||
| 151578461e | |||
| c219e1c5e6 | |||
| 701190a853 | |||
| 4a5d224e26 | |||
| c948ed897f | |||
| 9f83c4003a | |||
| 60ad89af8d | |||
| f59c3ac6e4 | |||
| 25aae80175 | |||
| 8a784023df | |||
| d105b3086e | |||
| e68734af4d | |||
| 80aca5c42e | |||
| 91cc351b9f | |||
| 3727eff0a1 | |||
| 98e8f2c77f | |||
| d9cc0ef9d3 | |||
| 20e5e555f1 | |||
| 156a6a6cb6 | |||
| 2c57c4598c | |||
| 130678682c | |||
| ca706b0281 | |||
| 9ff2b454fb | |||
| 5362042060 | |||
| 59c05047e6 | |||
| dabc32defa | |||
| d0ba4497b8 | |||
| cbdbb2cb4f | |||
| 8090db41fd | |||
| e4b8bf49c6 | |||
| f91e4f7f47 | |||
| 2185ab4d96 | |||
| 8b55326dbd | |||
| 29de3846b7 | |||
| 781f6703f2 | |||
| 7901b11a6d | |||
| 136351e574 | |||
| 004fe34221 | |||
| dc15afe84c | |||
| 298b4baeae | |||
| efa72f8d96 | |||
| d5d8cca96e | |||
| 34676028f9 | |||
| 6639a57225 | |||
| e084da09e7 | |||
| 8afc6b5c10 | |||
| 28b558e0eb | |||
| 18d55d8ef9 | |||
| ded62cb3f1 | |||
| da00ab06c2 | |||
| 4021aeef49 | |||
| 3cf5653f0d | |||
| 779e44f63a | |||
| 17f8d7bb98 | |||
| 47e0d157a2 | |||
| bcfaf764a4 | |||
| cd38fe180c | |||
| 51b54d0dc6 | |||
| a452163d96 | |||
| 1457492697 | |||
| c7ce387900 | |||
| 7131dbd052 | |||
| 1a2c8df31e | |||
| d2d280c2e3 | |||
| 7480c912b5 | |||
| 307fb2c8a3 | |||
| 85dcab4226 | |||
| da7bb49726 | |||
| 17c5b9b811 | |||
| 458ca8b40c | |||
| 9bf906a371 | |||
| b1e7ce7277 | |||
| 0fb461e3df | |||
| fdb9083e95 | |||
| ab645f0fe9 | |||
| 290ce48514 | |||
| b6c35b9f76 | |||
| 5c90e911b0 | |||
| b63181ae6b | |||
| bc72822fe4 | |||
| 29d6e2157c | |||
| 6b851b5033 | |||
| cad2a32394 | |||
| b251ba9709 | |||
| 812fb1ff32 | |||
| 0e299769bb | |||
| 956fed6616 | |||
| ef982e5d22 | |||
| fe589090d7 | |||
| 6100570511 | |||
| c25370304e | |||
| 203d561a5d | |||
| 987367d1ca | |||
| f7523e073f | |||
| f42fd8a333 | |||
| bf39b6c79e | |||
| 33dbbb5178 | |||
| db79948886 | |||
| cf09b40b4a | |||
| fbc5aedf35 | |||
| 31c0f1566b | |||
| 16786404e1 | |||
| 050d68c0d1 | |||
| 9f807e8e1d | |||
| 851c2d0239 | |||
| f9ab417fdf | |||
| 6702e0714f | |||
| fa16558cda | |||
| 5328e0be5d | |||
| 1bd02406d4 | |||
| a4c34fb326 | |||
| 80a70b758d | |||
| 0a333efddb | |||
| a4cf1a2957 | |||
| 59e5709f28 | |||
| 1195eb23b1 | |||
| 69f00977ae | |||
| fc8b8b8a9a | |||
| 07a3160ed5 | |||
| 1496fbfa53 | |||
| 780593c0b6 | |||
| db891f43ce | |||
| 3357d9a86b | |||
| 43a7ca4b5e | |||
| c764a356ce | |||
| 4a881b9eaf | |||
| 76d70ea861 | |||
| 959a04e1cc | |||
| bf8fbcc9e3 | |||
| f9fe46154a | |||
| c97921b2ea | |||
| 95967f2f45 | |||
| 552c05599f | |||
| 837d110796 | |||
| fcc7e84300 | |||
| 9080bc85f2 | |||
| 512f1a8f4d | |||
| 4faa6b9af4 | |||
| 019ee02912 |
127
addons/cx_web_refresh_from_backend/README.rst
Normal file
127
addons/cx_web_refresh_from_backend/README.rst
Normal file
@@ -0,0 +1,127 @@
|
||||
========================
|
||||
Web Refresh From Backend
|
||||
========================
|
||||
|
||||
..
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! This file is generated by oca-gen-addon-readme !!
|
||||
!! changes will be overwritten. !!
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! source digest: sha256:e841ff66d3bfff0a3de22c9be5dc40f1ca539739f5487a9162fdf887fc5ac6ad
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
|
||||
:target: https://odoo-community.org/page/development-status
|
||||
:alt: Beta
|
||||
.. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png
|
||||
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
|
||||
:alt: License: LGPL-3
|
||||
.. |badge3| image:: https://img.shields.io/badge/github-cetmix%2Fcetmix--tower-lightgray.png?logo=github
|
||||
:target: https://github.com/cetmix/cetmix-tower/tree/16.0/cx_web_refresh_from_backend
|
||||
:alt: cetmix/cetmix-tower
|
||||
|
||||
|badge1| |badge2| |badge3|
|
||||
|
||||
Backend UI Reload Module
|
||||
========================
|
||||
|
||||
This is a **technical module** that allows triggering a **UI reload**
|
||||
from the backend. It enables triggering the reload action for selected
|
||||
users and record IDs.
|
||||
|
||||
--------------
|
||||
|
||||
🔧 Helper Function: ``reload_views``
|
||||
------------------------------------
|
||||
|
||||
A special helper function ``reload_views`` is added to the ``res.users``
|
||||
model.
|
||||
|
||||
**Arguments**
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
+----------------+--------------------------+--------------------------+
|
||||
| Argument | Type | Description |
|
||||
+================+==========================+==========================+
|
||||
| **model** | ``Char`` | Model name, e.g. |
|
||||
| | | ``'res.partner'`` |
|
||||
+----------------+--------------------------+--------------------------+
|
||||
| **view_types** | ``List of Char`` | View types to reload, |
|
||||
| | *(optional)* | e.g. |
|
||||
| | | ``["form", "kanban"]``. |
|
||||
| | | Leave blank to reload |
|
||||
| | | all views. |
|
||||
+----------------+--------------------------+--------------------------+
|
||||
| **rec_ids** | ``List of Integer`` | The view will be |
|
||||
| | *(optional)* | reloaded only if a |
|
||||
| | | record with an ID from |
|
||||
| | | this list is present in |
|
||||
| | | the view. |
|
||||
+----------------+--------------------------+--------------------------+
|
||||
|
||||
--------------
|
||||
|
||||
⚠️ Important Notes
|
||||
------------------
|
||||
|
||||
Use this function **wisely**.
|
||||
|
||||
When reloading **form views**, be aware that if a user is currently
|
||||
editing a record, **their unsaved updates may be lost**.
|
||||
|
||||
**Table of contents**
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
🧩 Example Usage
|
||||
----------------
|
||||
|
||||
Below is a code snippet showing how to use the ``reload_views`` helper
|
||||
function.
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Reload the kanban and form views for all salespeople when an opportunity is won
|
||||
# Will reload views only if the current opportunity is being displayed
|
||||
|
||||
group_id = self.env.ref("sales_team.group_sale_salesman").id
|
||||
users_to_reload = self.env["res.users"].search([("groups_id", "in", [group_id])])
|
||||
users_to_reload.reload_views(
|
||||
model="crm.lead",
|
||||
view_types=["kanban", "form"],
|
||||
rec_ids=[self.id],
|
||||
)
|
||||
|
||||
Bug Tracker
|
||||
===========
|
||||
|
||||
Bugs are tracked on `GitHub Issues <https://github.com/cetmix/cetmix-tower/issues>`_.
|
||||
In case of trouble, please check there if your issue has already been reported.
|
||||
If you spotted it first, help us to smash it by providing a detailed and welcomed
|
||||
`feedback <https://github.com/cetmix/cetmix-tower/issues/new?body=module:%20cx_web_refresh_from_backend%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
|
||||
|
||||
Do not contact contributors directly about support or help with technical issues.
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
||||
Authors
|
||||
-------
|
||||
|
||||
* Cetmix
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
- Cetmix
|
||||
|
||||
Maintainers
|
||||
-----------
|
||||
|
||||
This module is part of the `cetmix/cetmix-tower <https://github.com/cetmix/cetmix-tower/tree/16.0/cx_web_refresh_from_backend>`_ project on GitHub.
|
||||
|
||||
You are welcome to contribute.
|
||||
4
addons/cx_web_refresh_from_backend/__init__.py
Normal file
4
addons/cx_web_refresh_from_backend/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# Copyright 2025 Cetmix OÜ
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||
|
||||
from . import models
|
||||
26
addons/cx_web_refresh_from_backend/__manifest__.py
Normal file
26
addons/cx_web_refresh_from_backend/__manifest__.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# Copyright 2025 Cetmix OÜ
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||
|
||||
# Mail is required: its ir.websocket override subscribes the partner channel to the
|
||||
# bus, so users receive web.refresh_view notifications.
|
||||
|
||||
{
|
||||
"name": "Web Refresh From Backend",
|
||||
"summary": "Refresh frontend views from backend",
|
||||
"version": "16.0.1.0.0",
|
||||
"category": "Web",
|
||||
"license": "LGPL-3",
|
||||
"author": "Cetmix",
|
||||
"website": "https://tower.cetmix.com",
|
||||
"images": ["static/description/banner.png"],
|
||||
"depends": ["mail"],
|
||||
"assets": {
|
||||
"web.assets_backend": [
|
||||
"cx_web_refresh_from_backend/static/src/views/list/list_controller_patch.esm.js",
|
||||
"cx_web_refresh_from_backend/static/src/views/kanban/kanban_controller_patch.esm.js",
|
||||
"cx_web_refresh_from_backend/static/src/views/form/form_controller_patch.esm.js",
|
||||
],
|
||||
},
|
||||
"installable": True,
|
||||
"auto_install": False,
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * cx_web_refresh_from_backend
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 16.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: cx_web_refresh_from_backend
|
||||
#. odoo-javascript
|
||||
#: code:addons/cx_web_refresh_from_backend/static/src/views/form/form_controller_patch.esm.js:0
|
||||
#, python-format
|
||||
msgid "All unsaved changes will be lost! Continue?"
|
||||
msgstr ""
|
||||
|
||||
#. module: cx_web_refresh_from_backend
|
||||
#. odoo-javascript
|
||||
#: code:addons/cx_web_refresh_from_backend/static/src/views/form/form_controller_patch.esm.js:0
|
||||
#: code:addons/cx_web_refresh_from_backend/static/src/views/list/list_controller_patch.esm.js:0
|
||||
#, python-format
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#. module: cx_web_refresh_from_backend
|
||||
#. odoo-javascript
|
||||
#: code:addons/cx_web_refresh_from_backend/static/src/views/form/form_controller_patch.esm.js:0
|
||||
#, python-format
|
||||
msgid "Continue"
|
||||
msgstr ""
|
||||
|
||||
#. module: cx_web_refresh_from_backend
|
||||
#. odoo-javascript
|
||||
#: code:addons/cx_web_refresh_from_backend/static/src/views/form/form_controller_patch.esm.js:0
|
||||
#, python-format
|
||||
msgid "Could not reload form. "
|
||||
msgstr ""
|
||||
|
||||
#. module: cx_web_refresh_from_backend
|
||||
#. odoo-javascript
|
||||
#: code:addons/cx_web_refresh_from_backend/static/src/views/kanban/kanban_controller_patch.esm.js:0
|
||||
#, python-format
|
||||
msgid "Could not reload kanban. "
|
||||
msgstr ""
|
||||
|
||||
#. module: cx_web_refresh_from_backend
|
||||
#. odoo-javascript
|
||||
#: code:addons/cx_web_refresh_from_backend/static/src/views/list/list_controller_patch.esm.js:0
|
||||
#, python-format
|
||||
msgid "Could not reload list. "
|
||||
msgstr ""
|
||||
|
||||
#. module: cx_web_refresh_from_backend
|
||||
#. odoo-javascript
|
||||
#: code:addons/cx_web_refresh_from_backend/static/src/views/list/list_controller_patch.esm.js:0
|
||||
#, python-format
|
||||
msgid "Could not save record. "
|
||||
msgstr ""
|
||||
|
||||
#. module: cx_web_refresh_from_backend
|
||||
#. odoo-javascript
|
||||
#: code:addons/cx_web_refresh_from_backend/static/src/views/form/form_controller_patch.esm.js:0
|
||||
#, python-format
|
||||
msgid "Form is being refreshed from backend"
|
||||
msgstr ""
|
||||
|
||||
#. module: cx_web_refresh_from_backend
|
||||
#. odoo-javascript
|
||||
#: code:addons/cx_web_refresh_from_backend/static/src/views/list/list_controller_patch.esm.js:0
|
||||
#, python-format
|
||||
msgid "List is being refreshed from backend"
|
||||
msgstr ""
|
||||
|
||||
#. module: cx_web_refresh_from_backend
|
||||
#. odoo-javascript
|
||||
#: code:addons/cx_web_refresh_from_backend/static/src/views/list/list_controller_patch.esm.js:0
|
||||
#, python-format
|
||||
msgid "Save & Refresh"
|
||||
msgstr ""
|
||||
|
||||
#. module: cx_web_refresh_from_backend
|
||||
#: model:ir.model,name:cx_web_refresh_from_backend.model_res_users
|
||||
msgid "User"
|
||||
msgstr ""
|
||||
|
||||
#. module: cx_web_refresh_from_backend
|
||||
#. odoo-javascript
|
||||
#: code:addons/cx_web_refresh_from_backend/static/src/views/list/list_controller_patch.esm.js:0
|
||||
#, python-format
|
||||
msgid "You have unsaved edits. Save them before refreshing?"
|
||||
msgstr ""
|
||||
4
addons/cx_web_refresh_from_backend/models/__init__.py
Normal file
4
addons/cx_web_refresh_from_backend/models/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# Copyright 2025 Cetmix OÜ
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||
|
||||
from . import res_users
|
||||
50
addons/cx_web_refresh_from_backend/models/res_users.py
Normal file
50
addons/cx_web_refresh_from_backend/models/res_users.py
Normal file
@@ -0,0 +1,50 @@
|
||||
# Copyright 2025 Cetmix OÜ
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class ResUsers(models.Model):
|
||||
_inherit = "res.users"
|
||||
|
||||
def reload_views(self, model, view_types=None, rec_ids=None):
|
||||
"""
|
||||
Trigger UI reload for selected users and record IDs.
|
||||
|
||||
This method allows to reload specific views from the backend.
|
||||
Be aware that when reloading form views, if a user is currently
|
||||
doing some updates, those updates may be lost.
|
||||
|
||||
:param model: str, Model name (e.g., 'res.partner')
|
||||
:param view_types: list of str, optional, View types to reload
|
||||
(e.g., ['form', 'kanban']). Leave blank to reload all views.
|
||||
:param rec_ids: list of int, optional, View will be reloaded only if a record
|
||||
with id from the list is present in the view.
|
||||
|
||||
Example usage:
|
||||
# Reload the kanban and form views for all salespeople
|
||||
# when an opportunity is won.
|
||||
# Will reload views only if the current opportunity is being displayed
|
||||
group_id = self.env.ref("sales_team.group_sale_salesman").id
|
||||
users_to_reload = self.env["res.users"].search(
|
||||
[("groups_id", "in", [group_id])]
|
||||
)
|
||||
users_to_reload.reload_views(
|
||||
model="crm.lead",
|
||||
view_types=["kanban", "form"],
|
||||
rec_ids=[self.id]
|
||||
)
|
||||
"""
|
||||
|
||||
# Prepare the message payload
|
||||
bus_message = {
|
||||
"model": model,
|
||||
"view_types": view_types or [],
|
||||
"rec_ids": rec_ids or [],
|
||||
}
|
||||
|
||||
# Send notification to each user's partner
|
||||
notifications = [
|
||||
[user.partner_id, "web.refresh_view", bus_message] for user in self
|
||||
]
|
||||
self.env["bus.bus"]._sendmany(notifications)
|
||||
3
addons/cx_web_refresh_from_backend/pyproject.toml
Normal file
3
addons/cx_web_refresh_from_backend/pyproject.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[build-system]
|
||||
requires = ["whool"]
|
||||
build-backend = "whool.buildapi"
|
||||
@@ -0,0 +1,2 @@
|
||||
* Cetmix
|
||||
|
||||
27
addons/cx_web_refresh_from_backend/readme/DESCRIPTION.md
Normal file
27
addons/cx_web_refresh_from_backend/readme/DESCRIPTION.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Backend UI Reload Module
|
||||
|
||||
This is a **technical module** that allows triggering a **UI reload** from the backend.
|
||||
It enables triggering the reload action for selected users and record IDs.
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Helper Function: `reload_views`
|
||||
|
||||
A special helper function `reload_views` is added to the `res.users` model.
|
||||
|
||||
### **Arguments**
|
||||
|
||||
| Argument | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| **model** | `Char` | Model name, e.g. `'res.partner'` |
|
||||
| **view_types** | `List of Char` *(optional)* | View types to reload, e.g. `["form", "kanban"]`. Leave blank to reload all views. |
|
||||
| **rec_ids** | `List of Integer` *(optional)* | The view will be reloaded only if a record with an ID from this list is present in the view. |
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Important Notes
|
||||
|
||||
Use this function **wisely**.
|
||||
|
||||
When reloading **form views**, be aware that if a user is currently editing a record,
|
||||
**their unsaved updates may be lost**.
|
||||
16
addons/cx_web_refresh_from_backend/readme/USAGE.md
Normal file
16
addons/cx_web_refresh_from_backend/readme/USAGE.md
Normal file
@@ -0,0 +1,16 @@
|
||||
## 🧩 Example Usage
|
||||
|
||||
Below is a code snippet showing how to use the `reload_views` helper function.
|
||||
|
||||
```python
|
||||
# Reload the kanban and form views for all salespeople when an opportunity is won
|
||||
# Will reload views only if the current opportunity is being displayed
|
||||
|
||||
group_id = self.env.ref("sales_team.group_sale_salesman").id
|
||||
users_to_reload = self.env["res.users"].search([("groups_id", "in", [group_id])])
|
||||
users_to_reload.reload_views(
|
||||
model="crm.lead",
|
||||
view_types=["kanban", "form"],
|
||||
rec_ids=[self.id],
|
||||
)
|
||||
```
|
||||
BIN
addons/cx_web_refresh_from_backend/static/description/banner.png
Normal file
BIN
addons/cx_web_refresh_from_backend/static/description/banner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 204 KiB |
BIN
addons/cx_web_refresh_from_backend/static/description/icon.png
Normal file
BIN
addons/cx_web_refresh_from_backend/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
484
addons/cx_web_refresh_from_backend/static/description/index.html
Normal file
484
addons/cx_web_refresh_from_backend/static/description/index.html
Normal file
@@ -0,0 +1,484 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
|
||||
<title>Web Refresh From Backend</title>
|
||||
<style type="text/css">
|
||||
|
||||
/*
|
||||
:Author: David Goodger (goodger@python.org)
|
||||
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
|
||||
:Copyright: This stylesheet has been placed in the public domain.
|
||||
|
||||
Default cascading style sheet for the HTML output of Docutils.
|
||||
Despite the name, some widely supported CSS2 features are used.
|
||||
|
||||
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
|
||||
customize this style sheet.
|
||||
*/
|
||||
|
||||
/* used to remove borders from tables and images */
|
||||
.borderless, table.borderless td, table.borderless th {
|
||||
border: 0 }
|
||||
|
||||
table.borderless td, table.borderless th {
|
||||
/* Override padding for "table.docutils td" with "! important".
|
||||
The right padding separates the table cells. */
|
||||
padding: 0 0.5em 0 0 ! important }
|
||||
|
||||
.first {
|
||||
/* Override more specific margin styles with "! important". */
|
||||
margin-top: 0 ! important }
|
||||
|
||||
.last, .with-subtitle {
|
||||
margin-bottom: 0 ! important }
|
||||
|
||||
.hidden {
|
||||
display: none }
|
||||
|
||||
.subscript {
|
||||
vertical-align: sub;
|
||||
font-size: smaller }
|
||||
|
||||
.superscript {
|
||||
vertical-align: super;
|
||||
font-size: smaller }
|
||||
|
||||
a.toc-backref {
|
||||
text-decoration: none ;
|
||||
color: black }
|
||||
|
||||
blockquote.epigraph {
|
||||
margin: 2em 5em ; }
|
||||
|
||||
dl.docutils dd {
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Uncomment (and remove this text!) to get bold-faced definition list terms
|
||||
dl.docutils dt {
|
||||
font-weight: bold }
|
||||
*/
|
||||
|
||||
div.abstract {
|
||||
margin: 2em 5em }
|
||||
|
||||
div.abstract p.topic-title {
|
||||
font-weight: bold ;
|
||||
text-align: center }
|
||||
|
||||
div.admonition, div.attention, div.caution, div.danger, div.error,
|
||||
div.hint, div.important, div.note, div.tip, div.warning {
|
||||
margin: 2em ;
|
||||
border: medium outset ;
|
||||
padding: 1em }
|
||||
|
||||
div.admonition p.admonition-title, div.hint p.admonition-title,
|
||||
div.important p.admonition-title, div.note p.admonition-title,
|
||||
div.tip p.admonition-title {
|
||||
font-weight: bold ;
|
||||
font-family: sans-serif }
|
||||
|
||||
div.attention p.admonition-title, div.caution p.admonition-title,
|
||||
div.danger p.admonition-title, div.error p.admonition-title,
|
||||
div.warning p.admonition-title, .code .error {
|
||||
color: red ;
|
||||
font-weight: bold ;
|
||||
font-family: sans-serif }
|
||||
|
||||
/* Uncomment (and remove this text!) to get reduced vertical space in
|
||||
compound paragraphs.
|
||||
div.compound .compound-first, div.compound .compound-middle {
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
div.compound .compound-last, div.compound .compound-middle {
|
||||
margin-top: 0.5em }
|
||||
*/
|
||||
|
||||
div.dedication {
|
||||
margin: 2em 5em ;
|
||||
text-align: center ;
|
||||
font-style: italic }
|
||||
|
||||
div.dedication p.topic-title {
|
||||
font-weight: bold ;
|
||||
font-style: normal }
|
||||
|
||||
div.figure {
|
||||
margin-left: 2em ;
|
||||
margin-right: 2em }
|
||||
|
||||
div.footer, div.header {
|
||||
clear: both;
|
||||
font-size: smaller }
|
||||
|
||||
div.line-block {
|
||||
display: block ;
|
||||
margin-top: 1em ;
|
||||
margin-bottom: 1em }
|
||||
|
||||
div.line-block div.line-block {
|
||||
margin-top: 0 ;
|
||||
margin-bottom: 0 ;
|
||||
margin-left: 1.5em }
|
||||
|
||||
div.sidebar {
|
||||
margin: 0 0 0.5em 1em ;
|
||||
border: medium outset ;
|
||||
padding: 1em ;
|
||||
background-color: #ffffee ;
|
||||
width: 40% ;
|
||||
float: right ;
|
||||
clear: right }
|
||||
|
||||
div.sidebar p.rubric {
|
||||
font-family: sans-serif ;
|
||||
font-size: medium }
|
||||
|
||||
div.system-messages {
|
||||
margin: 5em }
|
||||
|
||||
div.system-messages h1 {
|
||||
color: red }
|
||||
|
||||
div.system-message {
|
||||
border: medium outset ;
|
||||
padding: 1em }
|
||||
|
||||
div.system-message p.system-message-title {
|
||||
color: red ;
|
||||
font-weight: bold }
|
||||
|
||||
div.topic {
|
||||
margin: 2em }
|
||||
|
||||
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
|
||||
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
|
||||
margin-top: 0.4em }
|
||||
|
||||
h1.title {
|
||||
text-align: center }
|
||||
|
||||
h2.subtitle {
|
||||
text-align: center }
|
||||
|
||||
hr.docutils {
|
||||
width: 75% }
|
||||
|
||||
img.align-left, .figure.align-left, object.align-left, table.align-left {
|
||||
clear: left ;
|
||||
float: left ;
|
||||
margin-right: 1em }
|
||||
|
||||
img.align-right, .figure.align-right, object.align-right, table.align-right {
|
||||
clear: right ;
|
||||
float: right ;
|
||||
margin-left: 1em }
|
||||
|
||||
img.align-center, .figure.align-center, object.align-center {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
table.align-center {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left }
|
||||
|
||||
.align-center {
|
||||
clear: both ;
|
||||
text-align: center }
|
||||
|
||||
.align-right {
|
||||
text-align: right }
|
||||
|
||||
/* reset inner alignment in figures */
|
||||
div.align-right {
|
||||
text-align: inherit }
|
||||
|
||||
/* div.align-center * { */
|
||||
/* text-align: left } */
|
||||
|
||||
.align-top {
|
||||
vertical-align: top }
|
||||
|
||||
.align-middle {
|
||||
vertical-align: middle }
|
||||
|
||||
.align-bottom {
|
||||
vertical-align: bottom }
|
||||
|
||||
ol.simple, ul.simple {
|
||||
margin-bottom: 1em }
|
||||
|
||||
ol.arabic {
|
||||
list-style: decimal }
|
||||
|
||||
ol.loweralpha {
|
||||
list-style: lower-alpha }
|
||||
|
||||
ol.upperalpha {
|
||||
list-style: upper-alpha }
|
||||
|
||||
ol.lowerroman {
|
||||
list-style: lower-roman }
|
||||
|
||||
ol.upperroman {
|
||||
list-style: upper-roman }
|
||||
|
||||
p.attribution {
|
||||
text-align: right ;
|
||||
margin-left: 50% }
|
||||
|
||||
p.caption {
|
||||
font-style: italic }
|
||||
|
||||
p.credits {
|
||||
font-style: italic ;
|
||||
font-size: smaller }
|
||||
|
||||
p.label {
|
||||
white-space: nowrap }
|
||||
|
||||
p.rubric {
|
||||
font-weight: bold ;
|
||||
font-size: larger ;
|
||||
color: maroon ;
|
||||
text-align: center }
|
||||
|
||||
p.sidebar-title {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold ;
|
||||
font-size: larger }
|
||||
|
||||
p.sidebar-subtitle {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold }
|
||||
|
||||
p.topic-title {
|
||||
font-weight: bold }
|
||||
|
||||
pre.address {
|
||||
margin-bottom: 0 ;
|
||||
margin-top: 0 ;
|
||||
font: inherit }
|
||||
|
||||
pre.literal-block, pre.doctest-block, pre.math, pre.code {
|
||||
margin-left: 2em ;
|
||||
margin-right: 2em }
|
||||
|
||||
pre.code .ln { color: gray; } /* line numbers */
|
||||
pre.code, code { background-color: #eeeeee }
|
||||
pre.code .comment, code .comment { color: #5C6576 }
|
||||
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
|
||||
pre.code .literal.string, code .literal.string { color: #0C5404 }
|
||||
pre.code .name.builtin, code .name.builtin { color: #352B84 }
|
||||
pre.code .deleted, code .deleted { background-color: #DEB0A1}
|
||||
pre.code .inserted, code .inserted { background-color: #A3D289}
|
||||
|
||||
span.classifier {
|
||||
font-family: sans-serif ;
|
||||
font-style: oblique }
|
||||
|
||||
span.classifier-delimiter {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold }
|
||||
|
||||
span.interpreted {
|
||||
font-family: sans-serif }
|
||||
|
||||
span.option {
|
||||
white-space: nowrap }
|
||||
|
||||
span.pre {
|
||||
white-space: pre }
|
||||
|
||||
span.problematic, pre.problematic {
|
||||
color: red }
|
||||
|
||||
span.section-subtitle {
|
||||
/* font-size relative to parent (h1..h6 element) */
|
||||
font-size: 80% }
|
||||
|
||||
table.citation {
|
||||
border-left: solid 1px gray;
|
||||
margin-left: 1px }
|
||||
|
||||
table.docinfo {
|
||||
margin: 2em 4em }
|
||||
|
||||
table.docutils {
|
||||
margin-top: 0.5em ;
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
table.footnote {
|
||||
border-left: solid 1px black;
|
||||
margin-left: 1px }
|
||||
|
||||
table.docutils td, table.docutils th,
|
||||
table.docinfo td, table.docinfo th {
|
||||
padding-left: 0.5em ;
|
||||
padding-right: 0.5em ;
|
||||
vertical-align: top }
|
||||
|
||||
table.docutils th.field-name, table.docinfo th.docinfo-name {
|
||||
font-weight: bold ;
|
||||
text-align: left ;
|
||||
white-space: nowrap ;
|
||||
padding-left: 0 }
|
||||
|
||||
/* "booktabs" style (no vertical lines) */
|
||||
table.docutils.booktabs {
|
||||
border: 0px;
|
||||
border-top: 2px solid;
|
||||
border-bottom: 2px solid;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table.docutils.booktabs * {
|
||||
border: 0px;
|
||||
}
|
||||
table.docutils.booktabs th {
|
||||
border-bottom: thin solid;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
|
||||
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
|
||||
font-size: 100% }
|
||||
|
||||
ul.auto-toc {
|
||||
list-style-type: none }
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="document" id="web-refresh-from-backend">
|
||||
<h1 class="title">Web Refresh From Backend</h1>
|
||||
|
||||
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! This file is generated by oca-gen-addon-readme !!
|
||||
!! changes will be overwritten. !!
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! source digest: sha256:e841ff66d3bfff0a3de22c9be5dc40f1ca539739f5487a9162fdf887fc5ac6ad
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
|
||||
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/license-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/cetmix/cetmix-tower/tree/16.0/cx_web_refresh_from_backend"><img alt="cetmix/cetmix-tower" src="https://img.shields.io/badge/github-cetmix%2Fcetmix--tower-lightgray.png?logo=github" /></a></p>
|
||||
<div class="section" id="backend-ui-reload-module">
|
||||
<h1>Backend UI Reload Module</h1>
|
||||
<p>This is a <strong>technical module</strong> that allows triggering a <strong>UI reload</strong>
|
||||
from the backend. It enables triggering the reload action for selected
|
||||
users and record IDs.</p>
|
||||
<hr class="docutils" />
|
||||
<div class="section" id="helper-function-reload-views">
|
||||
<h2>🔧 Helper Function: <tt class="docutils literal">reload_views</tt></h2>
|
||||
<p>A special helper function <tt class="docutils literal">reload_views</tt> is added to the <tt class="docutils literal">res.users</tt>
|
||||
model.</p>
|
||||
<div class="section" id="arguments">
|
||||
<h3><strong>Arguments</strong></h3>
|
||||
<table border="1" class="docutils">
|
||||
<colgroup>
|
||||
<col width="24%" />
|
||||
<col width="38%" />
|
||||
<col width="38%" />
|
||||
</colgroup>
|
||||
<thead valign="bottom">
|
||||
<tr><th class="head">Argument</th>
|
||||
<th class="head">Type</th>
|
||||
<th class="head">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody valign="top">
|
||||
<tr><td><strong>model</strong></td>
|
||||
<td><tt class="docutils literal">Char</tt></td>
|
||||
<td>Model name, e.g.
|
||||
<tt class="docutils literal">'res.partner'</tt></td>
|
||||
</tr>
|
||||
<tr><td><strong>view_types</strong></td>
|
||||
<td><tt class="docutils literal">List of Char</tt>
|
||||
<em>(optional)</em></td>
|
||||
<td>View types to reload,
|
||||
e.g.
|
||||
<tt class="docutils literal">["form", "kanban"]</tt>.
|
||||
Leave blank to reload
|
||||
all views.</td>
|
||||
</tr>
|
||||
<tr><td><strong>rec_ids</strong></td>
|
||||
<td><tt class="docutils literal">List of Integer</tt>
|
||||
<em>(optional)</em></td>
|
||||
<td>The view will be
|
||||
reloaded only if a
|
||||
record with an ID from
|
||||
this list is present in
|
||||
the view.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="docutils" />
|
||||
<div class="section" id="important-notes">
|
||||
<h2>⚠️ Important Notes</h2>
|
||||
<p>Use this function <strong>wisely</strong>.</p>
|
||||
<p>When reloading <strong>form views</strong>, be aware that if a user is currently
|
||||
editing a record, <strong>their unsaved updates may be lost</strong>.</p>
|
||||
<p><strong>Table of contents</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="usage">
|
||||
<h1>Usage</h1>
|
||||
<div class="section" id="example-usage">
|
||||
<h2>🧩 Example Usage</h2>
|
||||
<p>Below is a code snippet showing how to use the <tt class="docutils literal">reload_views</tt> helper
|
||||
function.</p>
|
||||
<pre class="code python literal-block">
|
||||
<span class="c1"># Reload the kanban and form views for all salespeople when an opportunity is won</span><span class="w">
|
||||
</span><span class="c1"># Will reload views only if the current opportunity is being displayed</span><span class="w">
|
||||
|
||||
</span><span class="n">group_id</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">ref</span><span class="p">(</span><span class="s2">"sales_team.group_sale_salesman"</span><span class="p">)</span><span class="o">.</span><span class="n">id</span><span class="w">
|
||||
</span><span class="n">users_to_reload</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">env</span><span class="p">[</span><span class="s2">"res.users"</span><span class="p">]</span><span class="o">.</span><span class="n">search</span><span class="p">([(</span><span class="s2">"groups_id"</span><span class="p">,</span> <span class="s2">"in"</span><span class="p">,</span> <span class="p">[</span><span class="n">group_id</span><span class="p">])])</span><span class="w">
|
||||
</span><span class="n">users_to_reload</span><span class="o">.</span><span class="n">reload_views</span><span class="p">(</span><span class="w">
|
||||
</span> <span class="n">model</span><span class="o">=</span><span class="s2">"crm.lead"</span><span class="p">,</span><span class="w">
|
||||
</span> <span class="n">view_types</span><span class="o">=</span><span class="p">[</span><span class="s2">"kanban"</span><span class="p">,</span> <span class="s2">"form"</span><span class="p">],</span><span class="w">
|
||||
</span> <span class="n">rec_ids</span><span class="o">=</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">id</span><span class="p">],</span><span class="w">
|
||||
</span><span class="p">)</span>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="bug-tracker">
|
||||
<h1>Bug Tracker</h1>
|
||||
<p>Bugs are tracked on <a class="reference external" href="https://github.com/cetmix/cetmix-tower/issues">GitHub Issues</a>.
|
||||
In case of trouble, please check there if your issue has already been reported.
|
||||
If you spotted it first, help us to smash it by providing a detailed and welcomed
|
||||
<a class="reference external" href="https://github.com/cetmix/cetmix-tower/issues/new?body=module:%20cx_web_refresh_from_backend%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
|
||||
<p>Do not contact contributors directly about support or help with technical issues.</p>
|
||||
</div>
|
||||
<div class="section" id="credits">
|
||||
<h1>Credits</h1>
|
||||
<div class="section" id="authors">
|
||||
<h2>Authors</h2>
|
||||
<ul class="simple">
|
||||
<li>Cetmix</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="contributors">
|
||||
<h2>Contributors</h2>
|
||||
<ul class="simple">
|
||||
<li>Cetmix</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="maintainers">
|
||||
<h2>Maintainers</h2>
|
||||
<p>This module is part of the <a class="reference external" href="https://github.com/cetmix/cetmix-tower/tree/16.0/cx_web_refresh_from_backend">cetmix/cetmix-tower</a> project on GitHub.</p>
|
||||
<p>You are welcome to contribute.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,181 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import {FormController} from "@web/views/form/form_controller";
|
||||
import {patch} from "@web/core/utils/patch";
|
||||
import {useService} from "@web/core/utils/hooks";
|
||||
import {onWillUnmount} from "@odoo/owl";
|
||||
import {ConfirmationDialog} from "@web/core/confirmation_dialog/confirmation_dialog";
|
||||
|
||||
// Patch the standard FormController to react on bus notifications
|
||||
patch(FormController.prototype, "cx_web_refresh_from_backend.FormController", {
|
||||
setup() {
|
||||
// Call original setup logic
|
||||
this._super(...arguments);
|
||||
|
||||
// Get core services used by this behavior
|
||||
this.busService = useService("bus_service");
|
||||
this.actionService = useService("action");
|
||||
this.notificationService = useService("notification");
|
||||
|
||||
// Timestamp of last local save (used to avoid immediate auto-refresh)
|
||||
this._lastLocalSave = null;
|
||||
|
||||
// Bind the handler to keep reference for cleanup
|
||||
this._boundBusHandler = this._onBusNotification.bind(this);
|
||||
|
||||
// Subscribe to bus notifications
|
||||
this.busService.addEventListener("notification", this._boundBusHandler);
|
||||
|
||||
// Cleanup subscription on component unmount
|
||||
onWillUnmount(() => {
|
||||
if (this.busService && this._boundBusHandler) {
|
||||
this.busService.removeEventListener(
|
||||
"notification",
|
||||
this._boundBusHandler
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle bus notification for view refresh.
|
||||
* Listens for notifications with type "web.refresh_view" and delegates
|
||||
* processing to _handleViewRefresh.
|
||||
*
|
||||
* @param {Event} event - Bus notification event
|
||||
*/
|
||||
async _onBusNotification({detail: notifications}) {
|
||||
// Check if component is still alive
|
||||
if (!this.model || !this.model.root) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const {payload, type} of notifications) {
|
||||
if (type === "web.refresh_view") {
|
||||
await this._handleViewRefresh(payload);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle view refresh notification.
|
||||
*
|
||||
* Only refreshes when:
|
||||
* - model matches current form model
|
||||
* - requested view types include "form" (if specified)
|
||||
* - record id matches current record (if specified)
|
||||
*
|
||||
* @param {Object} notification - Notification payload
|
||||
*/
|
||||
async _handleViewRefresh(notification) {
|
||||
const {model, view_types = [], rec_ids = []} = notification;
|
||||
|
||||
// Check if the model matches current form model
|
||||
if (this.props.resModel !== model) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if view_type matches (if specified in notification)
|
||||
if (view_types.length > 0 && !view_types.includes("form")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if record ID matches (if rec_ids is specified)
|
||||
const currentResId = this.model && this.model.root && this.model.root.resId;
|
||||
if (rec_ids.length > 0 && (!currentResId || !rec_ids.includes(currentResId))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip refresh when form is in a dialog or when a wizard is on top of the stack.
|
||||
// Refreshing in that context can leave wizard/confirmation dialogs stuck open
|
||||
// (e.g. confirm="..." in wizard view).
|
||||
if (this.env.inDialog) {
|
||||
return;
|
||||
}
|
||||
const currentController = this.actionService.currentController;
|
||||
const currentAction = currentController && currentController.action;
|
||||
if (currentAction && currentAction.target === "new") {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.refreshForm();
|
||||
},
|
||||
|
||||
/**
|
||||
* Refresh the form with actual data from server.
|
||||
*
|
||||
* For normal forms:
|
||||
* - if record is clean: perform a soft_reload action
|
||||
* - if record has unsaved changes: ask for confirmation, then reload
|
||||
*
|
||||
* For wizards (dialogs, target="new"):
|
||||
* - reload only the current record without full action reload
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async refreshForm() {
|
||||
// Do not refresh immediately after an explicit save (debounce window)
|
||||
if (this._lastLocalSave && Date.now() - this._lastLocalSave < 1000) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.model || !this.model.root) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this form is opened as a wizard (dialog)
|
||||
const currentController = this.actionService.currentController;
|
||||
const action = currentController && currentController.action;
|
||||
const isWizard = action && action.target === "new";
|
||||
|
||||
const record = this.model.root;
|
||||
|
||||
if (!isWizard && record.isDirty) {
|
||||
// Ask user whether to discard unsaved changes before refreshing
|
||||
const confirmed = await new Promise((resolve) => {
|
||||
this.dialogService.add(ConfirmationDialog, {
|
||||
title: this.env._t("Form is being refreshed from backend"),
|
||||
body: this.env._t("All unsaved changes will be lost! Continue?"),
|
||||
confirm: () => resolve(true),
|
||||
cancel: () => resolve(false),
|
||||
confirmLabel: this.env._t("Continue"),
|
||||
cancelLabel: this.env._t("Cancel"),
|
||||
});
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await record.load();
|
||||
} catch (error) {
|
||||
const message =
|
||||
(error && error.data && error.data.message) ||
|
||||
(error && error.message) ||
|
||||
String(error);
|
||||
this.notificationService.add(
|
||||
this.env._t("Could not reload form. ") + message,
|
||||
{type: "danger"}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the view (only if component is still mounted)
|
||||
if (this.model && this.model.root) {
|
||||
this.render(true);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Override of save button handler.
|
||||
*
|
||||
* Stores timestamp of last local save to avoid immediate auto-refresh
|
||||
* triggered by our own changes.
|
||||
*/
|
||||
async saveButtonClicked() {
|
||||
this._lastLocalSave = Date.now();
|
||||
return await this._super(...arguments);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,137 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import {KanbanController} from "@web/views/kanban/kanban_controller";
|
||||
import {patch} from "@web/core/utils/patch";
|
||||
import {useService} from "@web/core/utils/hooks";
|
||||
import {onWillUnmount} from "@odoo/owl";
|
||||
|
||||
patch(KanbanController.prototype, "cx_web_refresh_from_backend.KanbanController", {
|
||||
setup() {
|
||||
this._super(...arguments);
|
||||
this.busService = useService("bus_service");
|
||||
this.notificationService = useService("notification");
|
||||
|
||||
// Bind the handler to keep reference for cleanup
|
||||
this._boundBusHandler = this._onBusNotification.bind(this);
|
||||
|
||||
// Subscribe to bus notifications
|
||||
this.busService.addEventListener("notification", this._boundBusHandler);
|
||||
|
||||
// Cleanup on unmount
|
||||
onWillUnmount(() => {
|
||||
if (this.busService && this._boundBusHandler) {
|
||||
this.busService.removeEventListener(
|
||||
"notification",
|
||||
this._boundBusHandler
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle bus notification for view refresh
|
||||
* @param {Event} event - Bus notification event
|
||||
*/
|
||||
async _onBusNotification({detail: notifications}) {
|
||||
// Check if component is still alive
|
||||
if (!this.model || !this.model.root) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const {payload, type} of notifications) {
|
||||
if (type === "web.refresh_view") {
|
||||
await this._handleViewRefresh(payload);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle view refresh notification
|
||||
* @param {Object} notification - Notification payload
|
||||
*/
|
||||
async _handleViewRefresh(notification) {
|
||||
const {model, view_types = [], rec_ids = []} = notification;
|
||||
|
||||
// Check if the model matches
|
||||
if (this.props.resModel !== model) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if view_type matches (if specified)
|
||||
if (view_types.length > 0 && !view_types.includes("kanban")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if record ID matches (if rec_ids is specified)
|
||||
if (rec_ids.length > 0) {
|
||||
const loadedIds = this.getLoadedRecordIds();
|
||||
const shouldReload = loadedIds.some((id) => rec_ids.includes(id));
|
||||
|
||||
if (!shouldReload) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await this.refreshList();
|
||||
},
|
||||
|
||||
/**
|
||||
* Refresh the kanban with actual data from server
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async refreshList() {
|
||||
// Safety check: component might be destroyed
|
||||
if (!this.model || !this.model.root) {
|
||||
return;
|
||||
}
|
||||
|
||||
const list = this.model.root;
|
||||
|
||||
// Reload data from server
|
||||
try {
|
||||
await list.load();
|
||||
} catch (error) {
|
||||
const message =
|
||||
(error && error.data && error.data.message) ||
|
||||
(error && error.message) ||
|
||||
String(error);
|
||||
this.notificationService.add(
|
||||
this.env._t("Could not reload kanban. ") + message,
|
||||
{type: "danger"}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the view (only if component is still mounted)
|
||||
if (this.model && this.model.root) {
|
||||
this.render(true);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get IDs of all loaded records on the current page
|
||||
* @returns {Array<Number>} Array of record IDs
|
||||
*/
|
||||
getLoadedRecordIds() {
|
||||
const list = this.model.root;
|
||||
|
||||
if (list.isGrouped) {
|
||||
// For grouped kanban, collect IDs from all groups
|
||||
const recordIds = [];
|
||||
const collectIds = (groups) => {
|
||||
for (const group of groups) {
|
||||
if (group.list && group.list.records) {
|
||||
recordIds.push(...group.list.records.map((r) => r.resId));
|
||||
}
|
||||
if (group.groups) {
|
||||
collectIds(group.groups);
|
||||
}
|
||||
}
|
||||
};
|
||||
collectIds(list.groups);
|
||||
return recordIds;
|
||||
}
|
||||
// For regular kanban, return IDs of all records
|
||||
return list.records.map((record) => record.resId);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,177 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import {ListController} from "@web/views/list/list_controller";
|
||||
import {patch} from "@web/core/utils/patch";
|
||||
import {useService} from "@web/core/utils/hooks";
|
||||
import {onWillUnmount} from "@odoo/owl";
|
||||
import {ConfirmationDialog} from "@web/core/confirmation_dialog/confirmation_dialog";
|
||||
|
||||
patch(ListController.prototype, "cx_web_refresh_from_backend.ListController", {
|
||||
setup() {
|
||||
this._super(...arguments);
|
||||
this.busService = useService("bus_service");
|
||||
this.dialogService = useService("dialog");
|
||||
this.notificationService = useService("notification");
|
||||
|
||||
// Bind the handler to keep reference for cleanup
|
||||
this._boundBusHandler = this._onBusNotification.bind(this);
|
||||
|
||||
// Subscribe to bus notifications
|
||||
this.busService.addEventListener("notification", this._boundBusHandler);
|
||||
|
||||
// Cleanup on unmount
|
||||
onWillUnmount(() => {
|
||||
if (this.busService && this._boundBusHandler) {
|
||||
this.busService.removeEventListener(
|
||||
"notification",
|
||||
this._boundBusHandler
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle bus notification for view refresh
|
||||
* @param {Event} event - Bus notification event
|
||||
*/
|
||||
async _onBusNotification({detail: notifications}) {
|
||||
// Check if component is still alive
|
||||
if (!this.model || !this.model.root) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const {payload, type} of notifications) {
|
||||
if (type === "web.refresh_view") {
|
||||
await this._handleViewRefresh(payload);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle view refresh notification
|
||||
* @param {Object} notification - Notification payload
|
||||
*/
|
||||
async _handleViewRefresh(notification) {
|
||||
const {model, view_types = [], rec_ids = []} = notification;
|
||||
|
||||
// Check if the model matches
|
||||
if (this.props.resModel !== model) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if view_type matches (if specified)
|
||||
if (
|
||||
view_types.length > 0 &&
|
||||
!view_types.includes("list") &&
|
||||
!view_types.includes("tree")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if record ID matches (if rec_ids is specified)
|
||||
if (rec_ids.length > 0) {
|
||||
const loadedIds = this.getLoadedRecordIds();
|
||||
const shouldReload = loadedIds.some((id) => rec_ids.includes(id));
|
||||
|
||||
if (!shouldReload) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await this.refreshList();
|
||||
},
|
||||
|
||||
/**
|
||||
* Refresh the list with actual data from server.
|
||||
* If there is an edited record, asks the user to save or cancel.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async refreshList() {
|
||||
// Safety check: component might be destroyed
|
||||
if (!this.model || !this.model.root) {
|
||||
return;
|
||||
}
|
||||
|
||||
const list = this.model.root;
|
||||
|
||||
if (list.editedRecord) {
|
||||
const confirmed = await new Promise((resolve) => {
|
||||
this.dialogService.add(ConfirmationDialog, {
|
||||
title: this.env._t("List is being refreshed from backend"),
|
||||
body: this.env._t(
|
||||
"You have unsaved edits. Save them before refreshing?"
|
||||
),
|
||||
confirm: () => resolve(true),
|
||||
cancel: () => resolve(false),
|
||||
confirmLabel: this.env._t("Save & Refresh"),
|
||||
cancelLabel: this.env._t("Cancel"),
|
||||
});
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await list.editedRecord.save();
|
||||
} catch (error) {
|
||||
const message =
|
||||
(error && error.data && error.data.message) ||
|
||||
(error && error.message) ||
|
||||
String(error);
|
||||
this.notificationService.add(
|
||||
this.env._t("Could not save record. ") + message,
|
||||
{type: "danger"}
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Reload data from server
|
||||
try {
|
||||
await list.load();
|
||||
} catch (error) {
|
||||
const message =
|
||||
(error && error.data && error.data.message) ||
|
||||
(error && error.message) ||
|
||||
String(error);
|
||||
this.notificationService.add(
|
||||
this.env._t("Could not reload list. ") + message,
|
||||
{type: "danger"}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the view (only if component is still mounted)
|
||||
if (this.model && this.model.root) {
|
||||
this.render(true);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get IDs of all loaded records on the current page
|
||||
* @returns {Array<Number>} Array of record IDs
|
||||
*/
|
||||
getLoadedRecordIds() {
|
||||
const list = this.model.root;
|
||||
|
||||
if (list.isGrouped) {
|
||||
// For grouped list, collect IDs from all groups
|
||||
const recordIds = [];
|
||||
const collectIds = (groups) => {
|
||||
for (const group of groups) {
|
||||
if (group.list && group.list.records) {
|
||||
recordIds.push(...group.list.records.map((r) => r.resId));
|
||||
}
|
||||
if (group.groups) {
|
||||
collectIds(group.groups);
|
||||
}
|
||||
}
|
||||
};
|
||||
collectIds(list.groups);
|
||||
return recordIds;
|
||||
}
|
||||
// For regular list, return IDs of all records
|
||||
return list.records.map((record) => record.resId);
|
||||
},
|
||||
});
|
||||
4
addons/cx_web_refresh_from_backend/tests/__init__.py
Normal file
4
addons/cx_web_refresh_from_backend/tests/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# Copyright 2025 Cetmix OÜ
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||
|
||||
from . import test_reload_views
|
||||
@@ -0,0 +1,95 @@
|
||||
# Copyright 2025 Cetmix OÜ
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
@tagged("post_install", "-at_install")
|
||||
class TestReloadViews(TransactionCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.user_admin = cls.env.ref("base.user_admin")
|
||||
cls.user_demo = cls.env.ref("base.user_demo")
|
||||
cls.partner = cls.env["res.partner"].create(
|
||||
{
|
||||
"name": "Test Partner",
|
||||
}
|
||||
)
|
||||
|
||||
def test_reload_views_basic(self):
|
||||
"""Test basic reload_views call without parameters"""
|
||||
with patch.object(type(self.env["bus.bus"]), "_sendmany") as mock_sendmany:
|
||||
self.user_admin.reload_views(model="res.partner")
|
||||
|
||||
mock_sendmany.assert_called_once()
|
||||
# Get the notifications list - it's the first positional argument
|
||||
notifications = mock_sendmany.call_args[0][0]
|
||||
self.assertEqual(len(notifications), 1)
|
||||
|
||||
partner, channel, message = notifications[0]
|
||||
self.assertEqual(partner, self.user_admin.partner_id)
|
||||
self.assertEqual(channel, "web.refresh_view")
|
||||
self.assertEqual(message["model"], "res.partner")
|
||||
self.assertEqual(message["view_types"], [])
|
||||
self.assertEqual(message["rec_ids"], [])
|
||||
|
||||
def test_reload_views_with_params(self):
|
||||
"""Test reload_views with view_types and rec_ids parameters"""
|
||||
with patch.object(type(self.env["bus.bus"]), "_sendmany") as mock_sendmany:
|
||||
self.user_admin.reload_views(
|
||||
model="res.partner",
|
||||
view_types=["form", "kanban"],
|
||||
rec_ids=[self.partner.id],
|
||||
)
|
||||
|
||||
notifications = mock_sendmany.call_args[0][0]
|
||||
message = notifications[0][2]
|
||||
self.assertEqual(message["view_types"], ["form", "kanban"])
|
||||
self.assertEqual(message["rec_ids"], [self.partner.id])
|
||||
|
||||
def test_reload_views_multiple_users(self):
|
||||
"""Test reload_views for multiple users at once"""
|
||||
users = self.user_admin | self.user_demo
|
||||
|
||||
with patch.object(type(self.env["bus.bus"]), "_sendmany") as mock_sendmany:
|
||||
users.reload_views(model="res.partner")
|
||||
|
||||
notifications = mock_sendmany.call_args[0][0]
|
||||
self.assertEqual(len(notifications), 2)
|
||||
|
||||
# Verify both users' partners are notified
|
||||
notified_partners = {n[0] for n in notifications}
|
||||
expected_partners = {self.user_admin.partner_id, self.user_demo.partner_id}
|
||||
self.assertEqual(notified_partners, expected_partners)
|
||||
|
||||
def test_reload_views_recordset(self):
|
||||
"""Test reload_views on a multi-record user recordset.
|
||||
|
||||
Ensures that calling reload_views on a recordset still results in a
|
||||
single _sendmany call, with one notification entry per user.
|
||||
"""
|
||||
users = self.user_admin | self.user_demo
|
||||
|
||||
with patch.object(type(self.env["bus.bus"]), "_sendmany") as mock_sendmany:
|
||||
users.reload_views(model="res.partner")
|
||||
|
||||
# _sendmany should be called only once for the whole recordset
|
||||
mock_sendmany.assert_called_once()
|
||||
|
||||
notifications = mock_sendmany.call_args[0][0]
|
||||
# We expect one notification tuple per user in the recordset
|
||||
self.assertEqual(len(notifications), 2)
|
||||
|
||||
# Verify both users' partners are notified and payload is correct
|
||||
for partner, channel, message in notifications:
|
||||
self.assertIn(
|
||||
partner, {self.user_admin.partner_id, self.user_demo.partner_id}
|
||||
)
|
||||
self.assertEqual(channel, "web.refresh_view")
|
||||
self.assertEqual(message["model"], "res.partner")
|
||||
self.assertEqual(message["view_types"], [])
|
||||
self.assertEqual(message["rec_ids"], [])
|
||||
Reference in New Issue
Block a user