test_fill_temporal.py 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911
  1. # -*- coding: utf-8 -*-
  2. """Test for fill temporal."""
  3. from odoo.tests import common
  4. class TestFillTemporal(common.TransactionCase):
  5. """Test for fill temporal.
  6. This feature is mainly used in graph view. For more informations, read the
  7. documentation of models's '_read_group_fill_temporal' method.
  8. """
  9. def setUp(self):
  10. super(TestFillTemporal, self).setUp()
  11. self.Model = self.env['test_read_group.fill_temporal']
  12. def test_date_range_and_flag(self):
  13. """Simple date range test, the flag is also tested.
  14. One of the most simple test. It must verify that dates 'holes' are filled
  15. only when the fill_temporal flag is set.
  16. """
  17. self.Model.create({'date': '1916-08-18', 'value': 2})
  18. self.Model.create({'date': '1916-10-19', 'value': 3})
  19. self.Model.create({'date': '1916-12-19', 'value': 5})
  20. expected = [{
  21. '__domain': ['&', ('date', '>=', '1916-08-01'), ('date', '<', '1916-09-01')],
  22. '__range': {'date': {'from': '1916-08-01', 'to': '1916-09-01'}},
  23. 'date': 'August 1916',
  24. 'date_count': 1,
  25. 'value': 2
  26. }, {
  27. '__domain': ['&', ('date', '>=', '1916-09-01'), ('date', '<', '1916-10-01')],
  28. '__range': {'date': {'from': '1916-09-01', 'to': '1916-10-01'}},
  29. 'date': 'September 1916',
  30. 'date_count': 0,
  31. 'value': False
  32. }, {
  33. '__domain': ['&', ('date', '>=', '1916-10-01'), ('date', '<', '1916-11-01')],
  34. '__range': {'date': {'from': '1916-10-01', 'to': '1916-11-01'}},
  35. 'date': 'October 1916',
  36. 'date_count': 1,
  37. 'value': 3
  38. }, {
  39. '__domain': ['&', ('date', '>=', '1916-11-01'), ('date', '<', '1916-12-01')],
  40. '__range': {'date': {'from': '1916-11-01', 'to': '1916-12-01'}},
  41. 'date': 'November 1916',
  42. 'date_count': 0,
  43. 'value': False
  44. }, {
  45. '__domain': ['&', ('date', '>=', '1916-12-01'), ('date', '<', '1917-01-01')],
  46. '__range': {'date': {'from': '1916-12-01', 'to': '1917-01-01'}},
  47. 'date': 'December 1916',
  48. 'date_count': 1,
  49. 'value': 5
  50. }]
  51. groups = self.Model.read_group([], fields=['date', 'value'], groupby=['date'])
  52. self.assertEqual(groups, [group for group in expected if group['date_count']])
  53. model_fill = self.Model.with_context(fill_temporal=True)
  54. groups = model_fill.read_group([], fields=['date', 'value'], groupby=['date'])
  55. self.assertEqual(groups, expected)
  56. def test_date_range_with_context_timezone(self):
  57. """Test if date are date_trunced correctly by pgres.
  58. This test was added in attempt to fix a bug appearing with babel that
  59. we use to translate the dates. Typically after a daylight saving, A
  60. whole year was displayed in a graph like this (APR missing and OCT
  61. appearing twice) :
  62. JAN FEB MAR MAY JUN JUL AUG SEP OCT OCT NOV
  63. ^^^ ^^^
  64. """
  65. self.Model.create({'date': '1915-01-01', 'value': 3})
  66. self.Model.create({'date': '1916-01-01', 'value': 5})
  67. expected = [{
  68. '__domain': ['&', ('date', '>=', '1915-01-01'), ('date', '<', '1915-02-01')],
  69. '__range': {'date': {'from': '1915-01-01', 'to': '1915-02-01'}},
  70. 'date': 'January 1915',
  71. 'date_count': 1,
  72. 'value': 3
  73. }, {
  74. '__domain': ['&', ('date', '>=', '1915-02-01'), ('date', '<', '1915-03-01')],
  75. '__range': {'date': {'from': '1915-02-01', 'to': '1915-03-01'}},
  76. 'date': 'February 1915',
  77. 'date_count': 0,
  78. 'value': False
  79. }, {
  80. '__domain': ['&', ('date', '>=', '1915-03-01'), ('date', '<', '1915-04-01')],
  81. '__range': {'date': {'from': '1915-03-01', 'to': '1915-04-01'}},
  82. 'date': 'March 1915',
  83. 'date_count': 0,
  84. 'value': False
  85. }, {
  86. '__domain': ['&', ('date', '>=', '1915-04-01'), ('date', '<', '1915-05-01')],
  87. '__range': {'date': {'from': '1915-04-01', 'to': '1915-05-01'}},
  88. 'date': 'April 1915',
  89. 'date_count': 0,
  90. 'value': False
  91. }, {
  92. '__domain': ['&', ('date', '>=', '1915-05-01'), ('date', '<', '1915-06-01')],
  93. '__range': {'date': {'from': '1915-05-01', 'to': '1915-06-01'}},
  94. 'date': 'May 1915',
  95. 'date_count': 0,
  96. 'value': False
  97. }, {
  98. '__domain': ['&', ('date', '>=', '1915-06-01'), ('date', '<', '1915-07-01')],
  99. '__range': {'date': {'from': '1915-06-01', 'to': '1915-07-01'}},
  100. 'date': 'June 1915',
  101. 'date_count': 0,
  102. 'value': False
  103. }, {
  104. '__domain': ['&', ('date', '>=', '1915-07-01'), ('date', '<', '1915-08-01')],
  105. '__range': {'date': {'from': '1915-07-01', 'to': '1915-08-01'}},
  106. 'date': 'July 1915',
  107. 'date_count': 0,
  108. 'value': False
  109. }, {
  110. '__domain': ['&', ('date', '>=', '1915-08-01'), ('date', '<', '1915-09-01')],
  111. '__range': {'date': {'from': '1915-08-01', 'to': '1915-09-01'}},
  112. 'date': 'August 1915',
  113. 'date_count': 0,
  114. 'value': False
  115. }, {
  116. '__domain': ['&', ('date', '>=', '1915-09-01'), ('date', '<', '1915-10-01')],
  117. '__range': {'date': {'from': '1915-09-01', 'to': '1915-10-01'}},
  118. 'date': 'September 1915',
  119. 'date_count': 0,
  120. 'value': False
  121. }, {
  122. '__domain': ['&', ('date', '>=', '1915-10-01'), ('date', '<', '1915-11-01')],
  123. '__range': {'date': {'from': '1915-10-01', 'to': '1915-11-01'}},
  124. 'date': 'October 1915',
  125. 'date_count': 0,
  126. 'value': False
  127. }, {
  128. '__domain': ['&', ('date', '>=', '1915-11-01'), ('date', '<', '1915-12-01')],
  129. '__range': {'date': {'from': '1915-11-01', 'to': '1915-12-01'}},
  130. 'date': 'November 1915',
  131. 'date_count': 0,
  132. 'value': False
  133. }, {
  134. '__domain': ['&', ('date', '>=', '1915-12-01'), ('date', '<', '1916-01-01')],
  135. '__range': {'date': {'from': '1915-12-01', 'to': '1916-01-01'}},
  136. 'date': 'December 1915',
  137. 'date_count': 0,
  138. 'value': False
  139. }, {
  140. '__domain': ['&', ('date', '>=', '1916-01-01'), ('date', '<', '1916-02-01')],
  141. '__range': {'date': {'from': '1916-01-01', 'to': '1916-02-01'}},
  142. 'date': 'January 1916',
  143. 'date_count': 1,
  144. 'value': 5
  145. }]
  146. # Time Zone UTC UTC DST
  147. tzs = ["America/Anchorage", # −09:00 −08:00
  148. "Europe/Brussels", # +01:00 +02:00
  149. "Pacific/Kwajalein"] # +12:00 +12:00
  150. for tz in tzs:
  151. model_fill = self.Model.with_context(tz=tz, fill_temporal=True)
  152. groups = model_fill.read_group([], fields=['date', 'value'], groupby=['date'])
  153. self.assertEqual(groups, expected)
  154. def test_only_with_only_null_date(self):
  155. """We should have the same result when fill_temporal is set or not."""
  156. self.Model.create({'date': False, 'value': 13})
  157. self.Model.create({'date': False, 'value': 11})
  158. self.Model.create({'date': False, 'value': 17})
  159. expected = [{'__domain': [('date', '=', False)],
  160. '__range': {'date': False},
  161. 'date_count': 3,
  162. 'value': 41,
  163. 'date': False}]
  164. groups = self.Model.read_group([], fields=['date', 'value'], groupby=['date'])
  165. self.assertEqual(groups, expected)
  166. model_fill = self.Model.with_context(fill_temporal=True)
  167. groups = model_fill.read_group([], fields=['date', 'value'], groupby=['date'])
  168. self.assertEqual(groups, expected)
  169. def test_date_range_and_null_date(self):
  170. """Test data with null and non-null dates."""
  171. self.Model.create({'date': '1916-08-19', 'value': 4})
  172. self.Model.create({'date': False, 'value': 13})
  173. self.Model.create({'date': '1916-10-18', 'value': 5})
  174. self.Model.create({'date': '1916-08-18', 'value': 3})
  175. self.Model.create({'date': '1916-10-19', 'value': 4})
  176. self.Model.create({'date': False, 'value': 11})
  177. expected = [{
  178. '__domain': ['&', ('date', '>=', '1916-08-01'), ('date', '<', '1916-09-01')],
  179. '__range': {'date': {'from': '1916-08-01', 'to': '1916-09-01'}},
  180. 'date': 'August 1916',
  181. 'date_count': 2,
  182. 'value': 7
  183. }, {
  184. '__domain': ['&', ('date', '>=', '1916-09-01'), ('date', '<', '1916-10-01')],
  185. '__range': {'date': {'from': '1916-09-01', 'to': '1916-10-01'}},
  186. 'date': 'September 1916',
  187. 'date_count': 0,
  188. 'value': 0
  189. }, {
  190. '__domain': ['&', ('date', '>=', '1916-10-01'), ('date', '<', '1916-11-01')],
  191. '__range': {'date': {'from': '1916-10-01', 'to': '1916-11-01'}},
  192. 'date': 'October 1916',
  193. 'date_count': 2,
  194. 'value': 9
  195. }, {
  196. '__domain': [('date', '=', False)],
  197. '__range': {'date': False},
  198. 'date': False,
  199. 'date_count': 2,
  200. 'value': 24
  201. }]
  202. groups = self.Model.read_group([], fields=['date', 'value'], groupby=['date'])
  203. self.assertEqual(groups, [group for group in expected if group['date_count']])
  204. model_fill = self.Model.with_context(fill_temporal=True)
  205. groups = model_fill.read_group([], fields=['date', 'value'], groupby=['date'])
  206. self.assertEqual(groups, expected)
  207. def test_order_date_desc(self):
  208. """Test if changing Model._order has influence on the result."""
  209. self.Model.create({'date': '1916-08-18', 'value': 3})
  210. self.Model.create({'date': '1916-08-19', 'value': 4})
  211. self.Model.create({'date': '1916-10-18', 'value': 5})
  212. self.Model.create({'date': '1916-10-19', 'value': 4})
  213. self.patch(type(self.Model), '_order', 'date desc')
  214. expected = [{
  215. '__domain': ['&', ('date', '>=', '1916-08-01'), ('date', '<', '1916-09-01')],
  216. '__range': {'date': {'from': '1916-08-01', 'to': '1916-09-01'}},
  217. 'date': 'August 1916',
  218. 'date_count': 2,
  219. 'value': 7
  220. }, {
  221. '__domain': ['&', ('date', '>=', '1916-09-01'), ('date', '<', '1916-10-01')],
  222. '__range': {'date': {'from': '1916-09-01', 'to': '1916-10-01'}},
  223. 'date': 'September 1916',
  224. 'date_count': 0,
  225. 'value': False
  226. }, {
  227. '__domain': ['&', ('date', '>=', '1916-10-01'), ('date', '<', '1916-11-01')],
  228. '__range': {'date': {'from': '1916-10-01', 'to': '1916-11-01'}},
  229. 'date': 'October 1916',
  230. 'date_count': 2,
  231. 'value': 9
  232. }]
  233. groups = self.Model.read_group([], fields=['date', 'value'], groupby=['date'])
  234. self.assertEqual(groups, [group for group in expected if group['date_count']])
  235. model_fill = self.Model.with_context(fill_temporal=True)
  236. groups = model_fill.read_group([], fields=['date', 'value'], groupby=['date'])
  237. self.assertEqual(groups, expected)
  238. def test_timestamp_without_timezone(self):
  239. """Test datetimes.
  240. Date stored with an hour inside the Odoo model are processed as timestamp
  241. without timezone by postgres.
  242. """
  243. self.Model.create({'datetime': '1916-08-19 01:30:00', 'value': 7})
  244. self.Model.create({'datetime': False, 'value': 13})
  245. self.Model.create({'datetime': '1916-10-18 02:30:00', 'value': 5})
  246. self.Model.create({'datetime': '1916-08-18 01:50:00', 'value': 3})
  247. self.Model.create({'datetime': False, 'value': 11})
  248. self.Model.create({'datetime': '1916-10-19 23:59:59', 'value': 2})
  249. self.Model.create({'datetime': '1916-10-19', 'value': 19})
  250. expected = [{
  251. '__domain': ['&',
  252. ('datetime', '>=', '1916-08-01 00:00:00'),
  253. ('datetime', '<', '1916-09-01 00:00:00')],
  254. '__range': {'datetime': {'from': '1916-08-01 00:00:00', 'to': '1916-09-01 00:00:00'}},
  255. 'datetime': 'August 1916',
  256. 'datetime_count': 2,
  257. 'value': 10
  258. }, {
  259. '__domain': ['&',
  260. ('datetime', '>=', '1916-09-01 00:00:00'),
  261. ('datetime', '<', '1916-10-01 00:00:00')],
  262. '__range': {'datetime': {'from': '1916-09-01 00:00:00', 'to': '1916-10-01 00:00:00'}},
  263. 'datetime': 'September 1916',
  264. 'datetime_count': 0,
  265. 'value': False
  266. }, {
  267. '__domain': ['&',
  268. ('datetime', '>=', '1916-10-01 00:00:00'),
  269. ('datetime', '<', '1916-11-01 00:00:00')],
  270. '__range': {'datetime': {'from': '1916-10-01 00:00:00', 'to': '1916-11-01 00:00:00'}},
  271. 'datetime': 'October 1916',
  272. 'datetime_count': 3,
  273. 'value': 26
  274. }, {
  275. '__domain': [('datetime', '=', False)],
  276. '__range': {'datetime': False},
  277. 'datetime': False,
  278. 'datetime_count': 2,
  279. 'value': 24
  280. }]
  281. groups = self.Model.read_group([], fields=['datetime', 'value'], groupby=['datetime'])
  282. self.assertEqual(groups, [group for group in expected if group['datetime_count']])
  283. model_fill = self.Model.with_context(fill_temporal=True)
  284. groups = model_fill.read_group([], fields=['datetime', 'value'], groupby=['datetime'])
  285. self.assertEqual(groups, expected)
  286. def test_with_datetimes_and_groupby_per_hour(self):
  287. """Test with datetimes and groupby per hour.
  288. Test if datetimes are filled correctly when grouping by hours instead of
  289. months.
  290. """
  291. self.Model.create({'datetime': '1916-01-01 01:30:00', 'value': 2})
  292. self.Model.create({'datetime': '1916-01-01 01:50:00', 'value': 8})
  293. self.Model.create({'datetime': '1916-01-01 02:30:00', 'value': 3})
  294. self.Model.create({'datetime': '1916-01-01 13:50:00', 'value': 5})
  295. self.Model.create({'datetime': '1916-01-01 23:50:00', 'value': 7})
  296. expected = [{
  297. '__domain': ['&',
  298. ('datetime', '>=', '1916-01-01 01:00:00'),
  299. ('datetime', '<', '1916-01-01 02:00:00')],
  300. '__range': {'datetime:hour': {'from': '1916-01-01 01:00:00', 'to': '1916-01-01 02:00:00'}},
  301. 'datetime:hour': '01:00 01 Jan',
  302. 'datetime_count': 2,
  303. 'value': 10
  304. }, {
  305. '__domain': ['&',
  306. ('datetime', '>=', '1916-01-01 02:00:00'),
  307. ('datetime', '<', '1916-01-01 03:00:00')],
  308. '__range': {'datetime:hour': {'from': '1916-01-01 02:00:00', 'to': '1916-01-01 03:00:00'}},
  309. 'datetime:hour': '02:00 01 Jan',
  310. 'datetime_count': 1,
  311. 'value': 3
  312. }, {
  313. '__domain': ['&',
  314. ('datetime', '>=', '1916-01-01 03:00:00'),
  315. ('datetime', '<', '1916-01-01 04:00:00')],
  316. '__range': {'datetime:hour': {'from': '1916-01-01 03:00:00', 'to': '1916-01-01 04:00:00'}},
  317. 'datetime:hour': '03:00 01 Jan',
  318. 'datetime_count': 0,
  319. 'value': False
  320. }, {
  321. '__domain': ['&',
  322. ('datetime', '>=', '1916-01-01 04:00:00'),
  323. ('datetime', '<', '1916-01-01 05:00:00')],
  324. '__range': {'datetime:hour': {'from': '1916-01-01 04:00:00', 'to': '1916-01-01 05:00:00'}},
  325. 'datetime:hour': '04:00 01 Jan',
  326. 'datetime_count': 0,
  327. 'value': False
  328. }, {
  329. '__domain': ['&',
  330. ('datetime', '>=', '1916-01-01 05:00:00'),
  331. ('datetime', '<', '1916-01-01 06:00:00')],
  332. '__range': {'datetime:hour': {'from': '1916-01-01 05:00:00', 'to': '1916-01-01 06:00:00'}},
  333. 'datetime:hour': '05:00 01 Jan',
  334. 'datetime_count': 0,
  335. 'value': False
  336. }, {
  337. '__domain': ['&',
  338. ('datetime', '>=', '1916-01-01 06:00:00'),
  339. ('datetime', '<', '1916-01-01 07:00:00')],
  340. '__range': {'datetime:hour': {'from': '1916-01-01 06:00:00', 'to': '1916-01-01 07:00:00'}},
  341. 'datetime:hour': '06:00 01 Jan',
  342. 'datetime_count': 0,
  343. 'value': False
  344. }, {
  345. '__domain': ['&',
  346. ('datetime', '>=', '1916-01-01 07:00:00'),
  347. ('datetime', '<', '1916-01-01 08:00:00')],
  348. '__range': {'datetime:hour': {'from': '1916-01-01 07:00:00', 'to': '1916-01-01 08:00:00'}},
  349. 'datetime:hour': '07:00 01 Jan',
  350. 'datetime_count': 0,
  351. 'value': False
  352. }, {
  353. '__domain': ['&',
  354. ('datetime', '>=', '1916-01-01 08:00:00'),
  355. ('datetime', '<', '1916-01-01 09:00:00')],
  356. '__range': {'datetime:hour': {'from': '1916-01-01 08:00:00', 'to': '1916-01-01 09:00:00'}},
  357. 'datetime:hour': '08:00 01 Jan',
  358. 'datetime_count': 0,
  359. 'value': False
  360. }, {
  361. '__domain': ['&',
  362. ('datetime', '>=', '1916-01-01 09:00:00'),
  363. ('datetime', '<', '1916-01-01 10:00:00')],
  364. '__range': {'datetime:hour': {'from': '1916-01-01 09:00:00', 'to': '1916-01-01 10:00:00'}},
  365. 'datetime:hour': '09:00 01 Jan',
  366. 'datetime_count': 0,
  367. 'value': False
  368. }, {
  369. '__domain': ['&',
  370. ('datetime', '>=', '1916-01-01 10:00:00'),
  371. ('datetime', '<', '1916-01-01 11:00:00')],
  372. '__range': {'datetime:hour': {'from': '1916-01-01 10:00:00', 'to': '1916-01-01 11:00:00'}},
  373. 'datetime:hour': '10:00 01 Jan',
  374. 'datetime_count': 0,
  375. 'value': False
  376. }, {
  377. '__domain': ['&',
  378. ('datetime', '>=', '1916-01-01 11:00:00'),
  379. ('datetime', '<', '1916-01-01 12:00:00')],
  380. '__range': {'datetime:hour': {'from': '1916-01-01 11:00:00', 'to': '1916-01-01 12:00:00'}},
  381. 'datetime:hour': '11:00 01 Jan',
  382. 'datetime_count': 0,
  383. 'value': False
  384. }, {
  385. '__domain': ['&',
  386. ('datetime', '>=', '1916-01-01 12:00:00'),
  387. ('datetime', '<', '1916-01-01 13:00:00')],
  388. '__range': {'datetime:hour': {'from': '1916-01-01 12:00:00', 'to': '1916-01-01 13:00:00'}},
  389. 'datetime:hour': '12:00 01 Jan',
  390. 'datetime_count': 0,
  391. 'value': False
  392. }, {
  393. '__domain': ['&',
  394. ('datetime', '>=', '1916-01-01 13:00:00'),
  395. ('datetime', '<', '1916-01-01 14:00:00')],
  396. '__range': {'datetime:hour': {'from': '1916-01-01 13:00:00', 'to': '1916-01-01 14:00:00'}},
  397. 'datetime:hour': '01:00 01 Jan',
  398. 'datetime_count': 1,
  399. 'value': 5
  400. }, {
  401. '__domain': ['&',
  402. ('datetime', '>=', '1916-01-01 14:00:00'),
  403. ('datetime', '<', '1916-01-01 15:00:00')],
  404. '__range': {'datetime:hour': {'from': '1916-01-01 14:00:00', 'to': '1916-01-01 15:00:00'}},
  405. 'datetime:hour': '02:00 01 Jan',
  406. 'datetime_count': 0,
  407. 'value': False
  408. }, {
  409. '__domain': ['&',
  410. ('datetime', '>=', '1916-01-01 15:00:00'),
  411. ('datetime', '<', '1916-01-01 16:00:00')],
  412. '__range': {'datetime:hour': {'from': '1916-01-01 15:00:00', 'to': '1916-01-01 16:00:00'}},
  413. 'datetime:hour': '03:00 01 Jan',
  414. 'datetime_count': 0,
  415. 'value': False
  416. }, {
  417. '__domain': ['&',
  418. ('datetime', '>=', '1916-01-01 16:00:00'),
  419. ('datetime', '<', '1916-01-01 17:00:00')],
  420. '__range': {'datetime:hour': {'from': '1916-01-01 16:00:00', 'to': '1916-01-01 17:00:00'}},
  421. 'datetime:hour': '04:00 01 Jan',
  422. 'datetime_count': 0,
  423. 'value': False
  424. }, {
  425. '__domain': ['&',
  426. ('datetime', '>=', '1916-01-01 17:00:00'),
  427. ('datetime', '<', '1916-01-01 18:00:00')],
  428. '__range': {'datetime:hour': {'from': '1916-01-01 17:00:00', 'to': '1916-01-01 18:00:00'}},
  429. 'datetime:hour': '05:00 01 Jan',
  430. 'datetime_count': 0,
  431. 'value': False
  432. }, {
  433. '__domain': ['&',
  434. ('datetime', '>=', '1916-01-01 18:00:00'),
  435. ('datetime', '<', '1916-01-01 19:00:00')],
  436. '__range': {'datetime:hour': {'from': '1916-01-01 18:00:00', 'to': '1916-01-01 19:00:00'}},
  437. 'datetime:hour': '06:00 01 Jan',
  438. 'datetime_count': 0,
  439. 'value': False
  440. }, {
  441. '__domain': ['&',
  442. ('datetime', '>=', '1916-01-01 19:00:00'),
  443. ('datetime', '<', '1916-01-01 20:00:00')],
  444. '__range': {'datetime:hour': {'from': '1916-01-01 19:00:00', 'to': '1916-01-01 20:00:00'}},
  445. 'datetime:hour': '07:00 01 Jan',
  446. 'datetime_count': 0,
  447. 'value': False
  448. }, {
  449. '__domain': ['&',
  450. ('datetime', '>=', '1916-01-01 20:00:00'),
  451. ('datetime', '<', '1916-01-01 21:00:00')],
  452. '__range': {'datetime:hour': {'from': '1916-01-01 20:00:00', 'to': '1916-01-01 21:00:00'}},
  453. 'datetime:hour': '08:00 01 Jan',
  454. 'datetime_count': 0,
  455. 'value': False
  456. }, {
  457. '__domain': ['&',
  458. ('datetime', '>=', '1916-01-01 21:00:00'),
  459. ('datetime', '<', '1916-01-01 22:00:00')],
  460. '__range': {'datetime:hour': {'from': '1916-01-01 21:00:00', 'to': '1916-01-01 22:00:00'}},
  461. 'datetime:hour': '09:00 01 Jan',
  462. 'datetime_count': 0,
  463. 'value': False
  464. }, {
  465. '__domain': ['&',
  466. ('datetime', '>=', '1916-01-01 22:00:00'),
  467. ('datetime', '<', '1916-01-01 23:00:00')],
  468. '__range': {'datetime:hour': {'from': '1916-01-01 22:00:00', 'to': '1916-01-01 23:00:00'}},
  469. 'datetime:hour': '10:00 01 Jan',
  470. 'datetime_count': 0,
  471. 'value': False
  472. }, {
  473. '__domain': ['&',
  474. ('datetime', '>=', '1916-01-01 23:00:00'),
  475. ('datetime', '<', '1916-01-02 00:00:00')],
  476. '__range': {'datetime:hour': {'from': '1916-01-01 23:00:00', 'to': '1916-01-02 00:00:00'}},
  477. 'datetime:hour': '11:00 01 Jan',
  478. 'datetime_count': 1,
  479. 'value': 7
  480. }]
  481. model_fill = self.Model.with_context(fill_temporal=True)
  482. groups = model_fill.read_group([], fields=['datetime', 'value'], groupby=['datetime:hour'])
  483. self.assertEqual(groups, expected)
  484. def test_hour_with_timezones(self):
  485. """Test hour with timezones.
  486. What we do here is similar to test_with_datetimes_and_groupby_per_hour
  487. but with a timezone in the user context.
  488. """
  489. self.Model.create({'datetime': '1915-12-31 22:30:00', 'value': 2})
  490. self.Model.create({'datetime': '1916-01-01 03:30:00', 'value': 3})
  491. expected = [{
  492. '__domain': ['&',
  493. ('datetime', '>=', '1915-12-31 22:00:00'),
  494. ('datetime', '<', '1915-12-31 23:00:00')],
  495. '__range': {'datetime:hour': {'from': '1915-12-31 22:00:00', 'to': '1915-12-31 23:00:00'}},
  496. 'datetime:hour': '04:00 01 Jan',
  497. 'datetime_count': 1,
  498. 'value': 2
  499. }, {
  500. '__domain': ['&',
  501. ('datetime', '>=', '1915-12-31 23:00:00'),
  502. ('datetime', '<', '1916-01-01 00:00:00')],
  503. '__range': {'datetime:hour': {'from': '1915-12-31 23:00:00', 'to': '1916-01-01 00:00:00'}},
  504. 'datetime:hour': '05:00 01 Jan',
  505. 'datetime_count': 0,
  506. 'value': False
  507. }, {
  508. '__domain': ['&',
  509. ('datetime', '>=', '1916-01-01 00:00:00'),
  510. ('datetime', '<', '1916-01-01 01:00:00')],
  511. '__range': {'datetime:hour': {'from': '1916-01-01 00:00:00', 'to': '1916-01-01 01:00:00'}},
  512. 'datetime:hour': '06:00 01 Jan',
  513. 'datetime_count': 0,
  514. 'value': False
  515. }, {
  516. '__domain': ['&',
  517. ('datetime', '>=', '1916-01-01 01:00:00'),
  518. ('datetime', '<', '1916-01-01 02:00:00')],
  519. '__range': {'datetime:hour': {'from': '1916-01-01 01:00:00', 'to': '1916-01-01 02:00:00'}},
  520. 'datetime:hour': '07:00 01 Jan',
  521. 'datetime_count': 0,
  522. 'value': False
  523. }, {
  524. '__domain': ['&',
  525. ('datetime', '>=', '1916-01-01 02:00:00'),
  526. ('datetime', '<', '1916-01-01 03:00:00')],
  527. '__range': {'datetime:hour': {'from': '1916-01-01 02:00:00', 'to': '1916-01-01 03:00:00'}},
  528. 'datetime:hour': '08:00 01 Jan',
  529. 'datetime_count': 0,
  530. 'value': False
  531. }, {
  532. '__domain': ['&',
  533. ('datetime', '>=', '1916-01-01 03:00:00'),
  534. ('datetime', '<', '1916-01-01 04:00:00')],
  535. '__range': {'datetime:hour': {'from': '1916-01-01 03:00:00', 'to': '1916-01-01 04:00:00'}},
  536. 'datetime:hour': '09:00 01 Jan',
  537. 'datetime_count': 1,
  538. 'value': 3
  539. }]
  540. model_fill = self.Model.with_context(tz='Asia/Hovd', fill_temporal=True)
  541. groups = model_fill.read_group([], fields=['datetime', 'value'],
  542. groupby=['datetime:hour'])
  543. self.assertEqual(groups, expected)
  544. def test_quarter_with_timezones(self):
  545. """Test quarter with timezones.
  546. We group year by quarter and check that it is consistent with timezone.
  547. """
  548. self.Model.create({'datetime': '2016-01-01 03:30:00', 'value': 2})
  549. self.Model.create({'datetime': '2016-12-30 22:30:00', 'value': 3})
  550. expected = [{
  551. '__domain': ['&',
  552. ('datetime', '>=', '2015-12-31 17:00:00'),
  553. ('datetime', '<', '2016-03-31 16:00:00')],
  554. '__range': {'datetime:quarter': {'from': '2015-12-31 17:00:00', 'to': '2016-03-31 16:00:00'}},
  555. 'datetime:quarter': 'Q1 2016',
  556. 'datetime_count': 1,
  557. 'value': 2
  558. }, {
  559. '__domain': ['&',
  560. ('datetime', '>=', '2016-03-31 16:00:00'),
  561. ('datetime', '<', '2016-06-30 16:00:00')],
  562. '__range': {'datetime:quarter': {'from': '2016-03-31 16:00:00', 'to': '2016-06-30 16:00:00'}},
  563. 'datetime:quarter': 'Q2 2016',
  564. 'datetime_count': 0,
  565. 'value': False
  566. }, {
  567. '__domain': ['&',
  568. ('datetime', '>=', '2016-06-30 16:00:00'),
  569. ('datetime', '<', '2016-09-30 17:00:00')],
  570. '__range': {'datetime:quarter': {'from': '2016-06-30 16:00:00', 'to': '2016-09-30 17:00:00'}},
  571. 'datetime:quarter': 'Q3 2016',
  572. 'datetime_count': 0,
  573. 'value': False
  574. }, {
  575. '__domain': ['&',
  576. ('datetime', '>=', '2016-09-30 17:00:00'),
  577. ('datetime', '<', '2016-12-31 17:00:00')],
  578. '__range': {'datetime:quarter': {'from': '2016-09-30 17:00:00', 'to': '2016-12-31 17:00:00'}},
  579. 'datetime:quarter': 'Q4 2016',
  580. 'datetime_count': 1,
  581. 'value': 3
  582. }]
  583. model_fill = self.Model.with_context(tz='Asia/Hovd', fill_temporal=True)
  584. groups = model_fill.read_group([], fields=['datetime', 'value'],
  585. groupby=['datetime:quarter'])
  586. self.assertEqual(groups, expected)
  587. def test_edge_fx_tz(self):
  588. """We test if different edge effect by using a different timezone from the user context
  589. Suppose a user resident near Hovd, a city in Mongolia. he sells a product
  590. at exacltly 4:00 AM on 1st January 2018. Using its context, that datetime
  591. is previously converted to UTC time by the ORM so as being stored properly
  592. inside the datebase. We are in winter time so 'Asia/Hovd' is UTC+7 :
  593. '2018-01-01 04:00:00' --> '2017-12-31 21:00:00'
  594. If that same user groups by datetime, we must ensure that the last
  595. displayed date is in January and not in December.
  596. """
  597. self.Model.create({'datetime': '2017-12-31 21:00:00', 'value': 42})
  598. expected = [{
  599. '__domain': ['&',
  600. ('datetime', '>=', '2017-12-31 17:00:00'),
  601. ('datetime', '<', '2018-01-31 17:00:00')],
  602. '__range': {'datetime': {'from': '2017-12-31 17:00:00', 'to': '2018-01-31 17:00:00'}},
  603. 'datetime': 'January 2018',
  604. 'datetime_count': 1,
  605. 'value': 42
  606. }]
  607. model_fill = self.Model.with_context(tz='Asia/Hovd', fill_temporal=True)
  608. groups = model_fill.read_group([], fields=['datetime', 'value'], groupby=['datetime'])
  609. self.assertEqual(groups, expected)
  610. def test_with_bounds(self):
  611. """Test the alternative dictionary format for the fill_temporal context key (fill_from, fill_to).
  612. We apply the fill_temporal logic only to a cibled portion of the result of a read_group.
  613. [fill_from, fill_to] are the inclusive bounds of this portion.
  614. Data outside those bounds will not be filtered out
  615. Bounds will be converted to the start of the period which they belong to (depending
  616. on the granularity of the groupby). This means that we can put any date of the period as the bound
  617. and it will still work.
  618. """
  619. self.Model.create({'date': '1916-02-15', 'value': 1})
  620. self.Model.create({'date': '1916-06-15', 'value': 2})
  621. self.Model.create({'date': '1916-11-15', 'value': 3})
  622. expected = [{
  623. '__domain': ['&', ('date', '>=', '1916-02-01'), ('date', '<', '1916-03-01')],
  624. '__range': {'date': {'from': '1916-02-01', 'to': '1916-03-01'}},
  625. 'date': 'February 1916',
  626. 'date_count': 1,
  627. 'value': 1
  628. }, {
  629. '__domain': ['&', ('date', '>=', '1916-05-01'), ('date', '<', '1916-06-01')],
  630. '__range': {'date': {'from': '1916-05-01', 'to': '1916-06-01'}},
  631. 'date': 'May 1916',
  632. 'date_count': 0,
  633. 'value': False
  634. }, {
  635. '__domain': ['&', ('date', '>=', '1916-06-01'), ('date', '<', '1916-07-01')],
  636. '__range': {'date': {'from': '1916-06-01', 'to': '1916-07-01'}},
  637. 'date': 'June 1916',
  638. 'date_count': 1,
  639. 'value': 2
  640. }, {
  641. '__domain': ['&', ('date', '>=', '1916-07-01'), ('date', '<', '1916-08-01')],
  642. '__range': {'date': {'from': '1916-07-01', 'to': '1916-08-01'}},
  643. 'date': 'July 1916',
  644. 'date_count': 0,
  645. 'value': False
  646. }, {
  647. '__domain': ['&', ('date', '>=', '1916-08-01'), ('date', '<', '1916-09-01')],
  648. '__range': {'date': {'from': '1916-08-01', 'to': '1916-09-01'}},
  649. 'date': 'August 1916',
  650. 'date_count': 0,
  651. 'value': False
  652. }, {
  653. '__domain': ['&', ('date', '>=', '1916-11-01'), ('date', '<', '1916-12-01')],
  654. '__range': {'date': {'from': '1916-11-01', 'to': '1916-12-01'}},
  655. 'date': 'November 1916',
  656. 'date_count': 1,
  657. 'value': 3
  658. }]
  659. model_fill = self.Model.with_context(fill_temporal={"fill_from": '1916-05-15', "fill_to": '1916-08-15'})
  660. groups = model_fill.read_group([], fields=['date', 'value'], groupby=['date'])
  661. self.assertEqual(groups, expected)
  662. def test_upper_bound(self):
  663. """Test the alternative dictionary format for the fill_temporal context key (fill_to).
  664. Same as with both bounds, but this time the first bound is the earliest group with data
  665. (since only fill_to is set)
  666. """
  667. self.Model.create({'date': '1916-02-15', 'value': 1})
  668. expected = [{
  669. '__domain': ['&', ('date', '>=', '1916-02-01'), ('date', '<', '1916-03-01')],
  670. '__range': {'date': {'from': '1916-02-01', 'to': '1916-03-01'}},
  671. 'date': 'February 1916',
  672. 'date_count': 1,
  673. 'value': 1
  674. }, {
  675. '__domain': ['&', ('date', '>=', '1916-03-01'), ('date', '<', '1916-04-01')],
  676. '__range': {'date': {'from': '1916-03-01', 'to': '1916-04-01'}},
  677. 'date': 'March 1916',
  678. 'date_count': 0,
  679. 'value': False
  680. }, {
  681. '__domain': ['&', ('date', '>=', '1916-04-01'), ('date', '<', '1916-05-01')],
  682. '__range': {'date': {'from': '1916-04-01', 'to': '1916-05-01'}},
  683. 'date': 'April 1916',
  684. 'date_count': 0,
  685. 'value': False
  686. }]
  687. model_fill = self.Model.with_context(fill_temporal={"fill_to": '1916-04-15'})
  688. groups = model_fill.read_group([], fields=['date', 'value'], groupby=['date'])
  689. self.assertEqual(groups, expected)
  690. def test_lower_bound(self):
  691. """Test the alternative dictionary format for the fill_temporal context key (fill_from).
  692. Same as with both bounds, but this time the second bound is the lastest group with data
  693. (since only fill_from is set)
  694. """
  695. self.Model.create({'date': '1916-04-15', 'value': 1})
  696. expected = [{
  697. '__domain': ['&', ('date', '>=', '1916-02-01'), ('date', '<', '1916-03-01')],
  698. '__range': {'date': {'from': '1916-02-01', 'to': '1916-03-01'}},
  699. 'date': 'February 1916',
  700. 'date_count': 0,
  701. 'value': False
  702. }, {
  703. '__domain': ['&', ('date', '>=', '1916-03-01'), ('date', '<', '1916-04-01')],
  704. '__range': {'date': {'from': '1916-03-01', 'to': '1916-04-01'}},
  705. 'date': 'March 1916',
  706. 'date_count': 0,
  707. 'value': False
  708. }, {
  709. '__domain': ['&', ('date', '>=', '1916-04-01'), ('date', '<', '1916-05-01')],
  710. '__range': {'date': {'from': '1916-04-01', 'to': '1916-05-01'}},
  711. 'date': 'April 1916',
  712. 'date_count': 1,
  713. 'value': 1
  714. }]
  715. model_fill = self.Model.with_context(fill_temporal={"fill_from": '1916-02-15'})
  716. groups = model_fill.read_group([], fields=['date', 'value'], groupby=['date'])
  717. self.assertEqual(groups, expected)
  718. def test_empty_context_key(self):
  719. """Test the alternative dictionary format for the fill_temporal context key.
  720. When fill_temporal context key is set to an empty dictionary, it must be equivalent to being True
  721. """
  722. self.Model.create({'date': '1916-02-15', 'value': 1})
  723. self.Model.create({'date': '1916-04-15', 'value': 2})
  724. expected = [{
  725. '__domain': ['&', ('date', '>=', '1916-02-01'), ('date', '<', '1916-03-01')],
  726. '__range': {'date': {'from': '1916-02-01', 'to': '1916-03-01'}},
  727. 'date': 'February 1916',
  728. 'date_count': 1,
  729. 'value': 1
  730. }, {
  731. '__domain': ['&', ('date', '>=', '1916-03-01'), ('date', '<', '1916-04-01')],
  732. '__range': {'date': {'from': '1916-03-01', 'to': '1916-04-01'}},
  733. 'date': 'March 1916',
  734. 'date_count': 0,
  735. 'value': False
  736. }, {
  737. '__domain': ['&', ('date', '>=', '1916-04-01'), ('date', '<', '1916-05-01')],
  738. '__range': {'date': {'from': '1916-04-01', 'to': '1916-05-01'}},
  739. 'date': 'April 1916',
  740. 'date_count': 1,
  741. 'value': 2
  742. }]
  743. model_fill = self.Model.with_context(fill_temporal={})
  744. groups = model_fill.read_group([], fields=['date', 'value'], groupby=['date'])
  745. self.assertEqual(groups, expected)
  746. def test_min_groups(self):
  747. """Test the alternative dictionary format for the fill_temporal context key (min_groups).
  748. We guarantee that at least a certain amount of contiguous groups is returned, from the
  749. earliest group with data.
  750. """
  751. self.Model.create({'date': '1916-02-15', 'value': 1})
  752. expected = [{
  753. '__domain': ['&', ('date', '>=', '1916-02-01'), ('date', '<', '1916-03-01')],
  754. '__range': {'date': {'from': '1916-02-01', 'to': '1916-03-01'}},
  755. 'date': 'February 1916',
  756. 'date_count': 1,
  757. 'value': 1
  758. }, {
  759. '__domain': ['&', ('date', '>=', '1916-03-01'), ('date', '<', '1916-04-01')],
  760. '__range': {'date': {'from': '1916-03-01', 'to': '1916-04-01'}},
  761. 'date': 'March 1916',
  762. 'date_count': 0,
  763. 'value': False
  764. }]
  765. model_fill = self.Model.with_context(fill_temporal={"min_groups": 2})
  766. groups = model_fill.read_group([], fields=['date', 'value'], groupby=['date'])
  767. self.assertEqual(groups, expected)
  768. def test_with_bounds_and_min_groups(self):
  769. """Test the alternative dictionary format for the fill_temporal context key (fill_from, fill_to, min_groups).
  770. We guarantee that at least a certain amount of contiguous groups is returned, from the
  771. fill_from bound. The fill_from bound has precedence over the first group with data regarding min_groups
  772. (min_groups will first try to anchor itself on fill_from, or, if not specified, on the first group with data).
  773. This amount is not restricted by the fill_to bound, so, if necessary, the fill_temporal
  774. logic will be applied until min_groups is guaranteed, even for groups later than fill_to
  775. Groups outside the specifed bounds are not counted as part of min_groups, unless added specifically
  776. to guarantee min_groups.
  777. """
  778. self.Model.create({'date': '1916-02-15', 'value': 1})
  779. self.Model.create({'date': '1916-06-15', 'value': 2})
  780. self.Model.create({'date': '1916-11-15', 'value': 3})
  781. expected = [{
  782. '__domain': ['&', ('date', '>=', '1916-02-01'), ('date', '<', '1916-03-01')],
  783. '__range': {'date': {'from': '1916-02-01', 'to': '1916-03-01'}},
  784. 'date': 'February 1916',
  785. 'date_count': 1,
  786. 'value': 1
  787. }, {
  788. '__domain': ['&', ('date', '>=', '1916-05-01'), ('date', '<', '1916-06-01')],
  789. '__range': {'date': {'from': '1916-05-01', 'to': '1916-06-01'}},
  790. 'date': 'May 1916',
  791. 'date_count': 0,
  792. 'value': False
  793. }, {
  794. '__domain': ['&', ('date', '>=', '1916-06-01'), ('date', '<', '1916-07-01')],
  795. '__range': {'date': {'from': '1916-06-01', 'to': '1916-07-01'}},
  796. 'date': 'June 1916',
  797. 'date_count': 1,
  798. 'value': 2
  799. }, {
  800. '__domain': ['&', ('date', '>=', '1916-07-01'), ('date', '<', '1916-08-01')],
  801. '__range': {'date': {'from': '1916-07-01', 'to': '1916-08-01'}},
  802. 'date': 'July 1916',
  803. 'date_count': 0,
  804. 'value': False
  805. }, {
  806. '__domain': ['&', ('date', '>=', '1916-08-01'), ('date', '<', '1916-09-01')],
  807. '__range': {'date': {'from': '1916-08-01', 'to': '1916-09-01'}},
  808. 'date': 'August 1916',
  809. 'date_count': 0,
  810. 'value': False
  811. }, {
  812. '__domain': ['&', ('date', '>=', '1916-11-01'), ('date', '<', '1916-12-01')],
  813. '__range': {'date': {'from': '1916-11-01', 'to': '1916-12-01'}},
  814. 'date': 'November 1916',
  815. 'date_count': 1,
  816. 'value': 3
  817. }]
  818. model_fill = self.Model.with_context(fill_temporal={"fill_from": '1916-05-15', "fill_to": '1916-07-15', "min_groups": 4})
  819. groups = model_fill.read_group([], fields=['date', 'value'], groupby=['date'])
  820. self.assertEqual(groups, expected)