wbfdm 2.2.1__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of wbfdm might be problematic. Click here for more details.

Files changed (337) hide show
  1. wbfdm/__init__.py +2 -0
  2. wbfdm/admin/__init__.py +42 -0
  3. wbfdm/admin/classifications.py +39 -0
  4. wbfdm/admin/esg.py +23 -0
  5. wbfdm/admin/exchanges.py +53 -0
  6. wbfdm/admin/instrument_lists.py +23 -0
  7. wbfdm/admin/instrument_prices.py +62 -0
  8. wbfdm/admin/instrument_requests.py +33 -0
  9. wbfdm/admin/instruments.py +117 -0
  10. wbfdm/admin/instruments_relationships.py +25 -0
  11. wbfdm/admin/options.py +101 -0
  12. wbfdm/analysis/__init__.py +2 -0
  13. wbfdm/analysis/esg/__init__.py +0 -0
  14. wbfdm/analysis/esg/enums.py +82 -0
  15. wbfdm/analysis/esg/esg_analysis.py +217 -0
  16. wbfdm/analysis/esg/utils.py +13 -0
  17. wbfdm/analysis/financial_analysis/__init__.py +1 -0
  18. wbfdm/analysis/financial_analysis/financial_metric_analysis.py +88 -0
  19. wbfdm/analysis/financial_analysis/financial_ratio_analysis.py +125 -0
  20. wbfdm/analysis/financial_analysis/financial_statistics_analysis.py +271 -0
  21. wbfdm/analysis/financial_analysis/statement_with_estimates.py +558 -0
  22. wbfdm/analysis/financial_analysis/utils.py +316 -0
  23. wbfdm/analysis/technical_analysis/__init__.py +1 -0
  24. wbfdm/analysis/technical_analysis/technical_analysis.py +138 -0
  25. wbfdm/analysis/technical_analysis/traces.py +165 -0
  26. wbfdm/analysis/utils.py +32 -0
  27. wbfdm/apps.py +14 -0
  28. wbfdm/contrib/__init__.py +0 -0
  29. wbfdm/contrib/dsws/__init__.py +0 -0
  30. wbfdm/contrib/dsws/client.py +285 -0
  31. wbfdm/contrib/internal/__init__.py +0 -0
  32. wbfdm/contrib/internal/dataloaders/__init__.py +0 -0
  33. wbfdm/contrib/internal/dataloaders/market_data.py +87 -0
  34. wbfdm/contrib/metric/__init__.py +0 -0
  35. wbfdm/contrib/metric/admin/__init__.py +2 -0
  36. wbfdm/contrib/metric/admin/instruments.py +12 -0
  37. wbfdm/contrib/metric/admin/metrics.py +43 -0
  38. wbfdm/contrib/metric/apps.py +10 -0
  39. wbfdm/contrib/metric/backends/__init__.py +2 -0
  40. wbfdm/contrib/metric/backends/base.py +159 -0
  41. wbfdm/contrib/metric/backends/performances.py +265 -0
  42. wbfdm/contrib/metric/backends/statistics.py +182 -0
  43. wbfdm/contrib/metric/decorators.py +14 -0
  44. wbfdm/contrib/metric/dispatch.py +23 -0
  45. wbfdm/contrib/metric/dto.py +88 -0
  46. wbfdm/contrib/metric/exceptions.py +6 -0
  47. wbfdm/contrib/metric/factories.py +33 -0
  48. wbfdm/contrib/metric/filters.py +28 -0
  49. wbfdm/contrib/metric/migrations/0001_initial.py +88 -0
  50. wbfdm/contrib/metric/migrations/0002_remove_instrumentmetric_unique_instrument_metric_and_more.py +26 -0
  51. wbfdm/contrib/metric/migrations/__init__.py +0 -0
  52. wbfdm/contrib/metric/models.py +180 -0
  53. wbfdm/contrib/metric/orchestrators.py +94 -0
  54. wbfdm/contrib/metric/registry.py +80 -0
  55. wbfdm/contrib/metric/serializers.py +44 -0
  56. wbfdm/contrib/metric/tasks.py +27 -0
  57. wbfdm/contrib/metric/tests/__init__.py +0 -0
  58. wbfdm/contrib/metric/tests/backends/__init__.py +0 -0
  59. wbfdm/contrib/metric/tests/backends/test_performances.py +152 -0
  60. wbfdm/contrib/metric/tests/backends/test_statistics.py +48 -0
  61. wbfdm/contrib/metric/tests/conftest.py +92 -0
  62. wbfdm/contrib/metric/tests/test_dto.py +73 -0
  63. wbfdm/contrib/metric/tests/test_models.py +72 -0
  64. wbfdm/contrib/metric/tests/test_tasks.py +24 -0
  65. wbfdm/contrib/metric/tests/test_viewsets.py +79 -0
  66. wbfdm/contrib/metric/urls.py +19 -0
  67. wbfdm/contrib/metric/viewsets/__init__.py +1 -0
  68. wbfdm/contrib/metric/viewsets/configs/__init__.py +1 -0
  69. wbfdm/contrib/metric/viewsets/configs/display.py +92 -0
  70. wbfdm/contrib/metric/viewsets/configs/menus.py +11 -0
  71. wbfdm/contrib/metric/viewsets/configs/utils.py +137 -0
  72. wbfdm/contrib/metric/viewsets/mixins.py +245 -0
  73. wbfdm/contrib/metric/viewsets/viewsets.py +40 -0
  74. wbfdm/contrib/msci/__init__.py +0 -0
  75. wbfdm/contrib/msci/client.py +92 -0
  76. wbfdm/contrib/msci/dataloaders/__init__.py +0 -0
  77. wbfdm/contrib/msci/dataloaders/esg.py +87 -0
  78. wbfdm/contrib/msci/dataloaders/esg_controversies.py +81 -0
  79. wbfdm/contrib/msci/sync.py +58 -0
  80. wbfdm/contrib/msci/tests/__init__.py +0 -0
  81. wbfdm/contrib/msci/tests/conftest.py +1 -0
  82. wbfdm/contrib/msci/tests/test_client.py +70 -0
  83. wbfdm/contrib/qa/__init__.py +0 -0
  84. wbfdm/contrib/qa/apps.py +22 -0
  85. wbfdm/contrib/qa/database_routers.py +25 -0
  86. wbfdm/contrib/qa/dataloaders/__init__.py +0 -0
  87. wbfdm/contrib/qa/dataloaders/adjustments.py +56 -0
  88. wbfdm/contrib/qa/dataloaders/corporate_actions.py +59 -0
  89. wbfdm/contrib/qa/dataloaders/financials.py +83 -0
  90. wbfdm/contrib/qa/dataloaders/market_data.py +117 -0
  91. wbfdm/contrib/qa/dataloaders/officers.py +59 -0
  92. wbfdm/contrib/qa/dataloaders/reporting_dates.py +67 -0
  93. wbfdm/contrib/qa/dataloaders/statements.py +267 -0
  94. wbfdm/contrib/qa/tasks.py +0 -0
  95. wbfdm/dataloaders/__init__.py +0 -0
  96. wbfdm/dataloaders/cache.py +129 -0
  97. wbfdm/dataloaders/protocols.py +112 -0
  98. wbfdm/dataloaders/proxies.py +201 -0
  99. wbfdm/dataloaders/types.py +209 -0
  100. wbfdm/dynamic_preferences_registry.py +45 -0
  101. wbfdm/enums.py +657 -0
  102. wbfdm/factories/__init__.py +13 -0
  103. wbfdm/factories/classifications.py +56 -0
  104. wbfdm/factories/controversies.py +27 -0
  105. wbfdm/factories/exchanges.py +21 -0
  106. wbfdm/factories/instrument_list.py +22 -0
  107. wbfdm/factories/instrument_prices.py +79 -0
  108. wbfdm/factories/instruments.py +63 -0
  109. wbfdm/factories/instruments_relationships.py +31 -0
  110. wbfdm/factories/options.py +66 -0
  111. wbfdm/figures/__init__.py +1 -0
  112. wbfdm/figures/financials/__init__.py +1 -0
  113. wbfdm/figures/financials/financial_analysis_charts.py +469 -0
  114. wbfdm/figures/financials/financials_charts.py +711 -0
  115. wbfdm/filters/__init__.py +31 -0
  116. wbfdm/filters/classifications.py +100 -0
  117. wbfdm/filters/exchanges.py +22 -0
  118. wbfdm/filters/financials.py +95 -0
  119. wbfdm/filters/financials_analysis.py +119 -0
  120. wbfdm/filters/instrument_prices.py +112 -0
  121. wbfdm/filters/instruments.py +198 -0
  122. wbfdm/filters/utils.py +44 -0
  123. wbfdm/import_export/__init__.py +0 -0
  124. wbfdm/import_export/backends/__init__.py +0 -0
  125. wbfdm/import_export/backends/cbinsights/__init__.py +2 -0
  126. wbfdm/import_export/backends/cbinsights/deals.py +44 -0
  127. wbfdm/import_export/backends/cbinsights/equities.py +41 -0
  128. wbfdm/import_export/backends/cbinsights/mixin.py +15 -0
  129. wbfdm/import_export/backends/cbinsights/utils/__init__.py +0 -0
  130. wbfdm/import_export/backends/cbinsights/utils/classifications.py +4150 -0
  131. wbfdm/import_export/backends/cbinsights/utils/client.py +217 -0
  132. wbfdm/import_export/backends/refinitiv/__init__.py +5 -0
  133. wbfdm/import_export/backends/refinitiv/daily_fundamental.py +36 -0
  134. wbfdm/import_export/backends/refinitiv/fiscal_period.py +63 -0
  135. wbfdm/import_export/backends/refinitiv/forecast.py +178 -0
  136. wbfdm/import_export/backends/refinitiv/fundamental.py +103 -0
  137. wbfdm/import_export/backends/refinitiv/geographic_segment.py +32 -0
  138. wbfdm/import_export/backends/refinitiv/instrument.py +55 -0
  139. wbfdm/import_export/backends/refinitiv/instrument_price.py +77 -0
  140. wbfdm/import_export/backends/refinitiv/mixin.py +29 -0
  141. wbfdm/import_export/backends/refinitiv/utils/__init__.py +1 -0
  142. wbfdm/import_export/backends/refinitiv/utils/controller.py +182 -0
  143. wbfdm/import_export/handlers/__init__.py +0 -0
  144. wbfdm/import_export/handlers/instrument.py +253 -0
  145. wbfdm/import_export/handlers/instrument_list.py +101 -0
  146. wbfdm/import_export/handlers/instrument_price.py +71 -0
  147. wbfdm/import_export/handlers/option.py +54 -0
  148. wbfdm/import_export/handlers/private_equities.py +49 -0
  149. wbfdm/import_export/parsers/__init__.py +0 -0
  150. wbfdm/import_export/parsers/cbinsights/__init__.py +0 -0
  151. wbfdm/import_export/parsers/cbinsights/deals.py +39 -0
  152. wbfdm/import_export/parsers/cbinsights/equities.py +56 -0
  153. wbfdm/import_export/parsers/cbinsights/fundamentals.py +45 -0
  154. wbfdm/import_export/parsers/refinitiv/__init__.py +0 -0
  155. wbfdm/import_export/parsers/refinitiv/daily_fundamental.py +7 -0
  156. wbfdm/import_export/parsers/refinitiv/forecast.py +7 -0
  157. wbfdm/import_export/parsers/refinitiv/fundamental.py +9 -0
  158. wbfdm/import_export/parsers/refinitiv/geographic_segment.py +7 -0
  159. wbfdm/import_export/parsers/refinitiv/instrument.py +75 -0
  160. wbfdm/import_export/parsers/refinitiv/instrument_price.py +26 -0
  161. wbfdm/import_export/parsers/refinitiv/utils.py +96 -0
  162. wbfdm/import_export/resources/__init__.py +0 -0
  163. wbfdm/import_export/resources/classification.py +23 -0
  164. wbfdm/import_export/resources/instrument_prices.py +33 -0
  165. wbfdm/import_export/resources/instruments.py +176 -0
  166. wbfdm/jinja2.py +7 -0
  167. wbfdm/management/__init__.py +30 -0
  168. wbfdm/menu.py +11 -0
  169. wbfdm/migrations/0001_initial.py +71 -0
  170. wbfdm/migrations/0002_rename_statements_instrumentlookup_financials_and_more.py +144 -0
  171. wbfdm/migrations/0003_instrument_estimate_backend_and_more.py +34 -0
  172. wbfdm/migrations/0004_rename_financials_instrumentlookup_statements_and_more.py +86 -0
  173. wbfdm/migrations/0005_instrument_corporate_action_backend.py +29 -0
  174. wbfdm/migrations/0006_instrument_officer_backend.py +29 -0
  175. wbfdm/migrations/0007_instrument_country_instrument_currency_and_more.py +117 -0
  176. wbfdm/migrations/0008_controversy.py +75 -0
  177. wbfdm/migrations/0009_alter_controversy_flag_alter_controversy_initiated_and_more.py +85 -0
  178. wbfdm/migrations/0010_classification_classificationgroup_deal_exchange_and_more.py +1299 -0
  179. wbfdm/migrations/0011_delete_instrumentlookup_instrument_corporate_actions_and_more.py +169 -0
  180. wbfdm/migrations/0012_instrumentprice_created_instrumentprice_modified.py +564 -0
  181. wbfdm/migrations/0013_instrument_is_investable_universe_and_more.py +199 -0
  182. wbfdm/migrations/0014_alter_controversy_instrument.py +22 -0
  183. wbfdm/migrations/0015_instrument_instrument_investible_index.py +16 -0
  184. wbfdm/migrations/0016_instrumenttype_name_repr.py +18 -0
  185. wbfdm/migrations/0017_instrument_instrument_security_index.py +16 -0
  186. wbfdm/migrations/0018_instrument_instrument_level_index.py +20 -0
  187. wbfdm/migrations/0019_alter_controversy_source.py +17 -0
  188. wbfdm/migrations/0020_optionaggregate_option_and_more.py +249 -0
  189. wbfdm/migrations/0021_delete_instrumentdailystatistics.py +15 -0
  190. wbfdm/migrations/0022_instrument_cusip_option_open_interest_20d_and_more.py +91 -0
  191. wbfdm/migrations/0023_instrument_unique_ric_instrument_unique_rmc_and_more.py +53 -0
  192. wbfdm/migrations/0024_option_open_interest_10d_option_volume_10d_and_more.py +36 -0
  193. wbfdm/migrations/0025_instrument_is_primary_and_more.py +29 -0
  194. wbfdm/migrations/0026_instrument_is_cash_equivalent.py +30 -0
  195. wbfdm/migrations/0027_remove_instrument_unique_ric_and_more.py +100 -0
  196. wbfdm/migrations/__init__.py +0 -0
  197. wbfdm/models/__init__.py +4 -0
  198. wbfdm/models/esg/__init__.py +1 -0
  199. wbfdm/models/esg/controversies.py +81 -0
  200. wbfdm/models/exchanges/__init__.py +1 -0
  201. wbfdm/models/exchanges/exchanges.py +223 -0
  202. wbfdm/models/fields.py +117 -0
  203. wbfdm/models/fk_fields.py +403 -0
  204. wbfdm/models/indicators.py +0 -0
  205. wbfdm/models/instruments/__init__.py +19 -0
  206. wbfdm/models/instruments/classifications.py +265 -0
  207. wbfdm/models/instruments/instrument_lists.py +120 -0
  208. wbfdm/models/instruments/instrument_prices.py +540 -0
  209. wbfdm/models/instruments/instrument_relationships.py +251 -0
  210. wbfdm/models/instruments/instrument_requests.py +196 -0
  211. wbfdm/models/instruments/instruments.py +991 -0
  212. wbfdm/models/instruments/llm/__init__.py +1 -0
  213. wbfdm/models/instruments/llm/create_instrument_news_relationships.py +78 -0
  214. wbfdm/models/instruments/mixin/__init__.py +0 -0
  215. wbfdm/models/instruments/mixin/financials_computed.py +804 -0
  216. wbfdm/models/instruments/mixin/financials_serializer_fields.py +1407 -0
  217. wbfdm/models/instruments/mixin/instruments.py +294 -0
  218. wbfdm/models/instruments/options.py +225 -0
  219. wbfdm/models/instruments/private_equities.py +59 -0
  220. wbfdm/models/instruments/querysets.py +73 -0
  221. wbfdm/models/instruments/utils.py +41 -0
  222. wbfdm/preferences.py +21 -0
  223. wbfdm/serializers/__init__.py +4 -0
  224. wbfdm/serializers/esg.py +36 -0
  225. wbfdm/serializers/exchanges.py +39 -0
  226. wbfdm/serializers/instruments/__init__.py +37 -0
  227. wbfdm/serializers/instruments/classifications.py +139 -0
  228. wbfdm/serializers/instruments/instrument_lists.py +61 -0
  229. wbfdm/serializers/instruments/instrument_prices.py +73 -0
  230. wbfdm/serializers/instruments/instrument_relationships.py +170 -0
  231. wbfdm/serializers/instruments/instrument_requests.py +61 -0
  232. wbfdm/serializers/instruments/instruments.py +274 -0
  233. wbfdm/serializers/instruments/mixins.py +104 -0
  234. wbfdm/serializers/officers.py +20 -0
  235. wbfdm/signals.py +7 -0
  236. wbfdm/sync/__init__.py +0 -0
  237. wbfdm/sync/abstract.py +31 -0
  238. wbfdm/sync/runner.py +22 -0
  239. wbfdm/tasks.py +69 -0
  240. wbfdm/tests/__init__.py +0 -0
  241. wbfdm/tests/analysis/__init__.py +0 -0
  242. wbfdm/tests/analysis/financial_analysis/__init__.py +0 -0
  243. wbfdm/tests/analysis/financial_analysis/test_statement_with_estimates.py +392 -0
  244. wbfdm/tests/analysis/financial_analysis/test_utils.py +322 -0
  245. wbfdm/tests/analysis/test_esg.py +159 -0
  246. wbfdm/tests/conftest.py +92 -0
  247. wbfdm/tests/dataloaders/__init__.py +0 -0
  248. wbfdm/tests/dataloaders/test_cache.py +73 -0
  249. wbfdm/tests/models/__init__.py +0 -0
  250. wbfdm/tests/models/test_classifications.py +99 -0
  251. wbfdm/tests/models/test_exchanges.py +7 -0
  252. wbfdm/tests/models/test_instrument_list.py +117 -0
  253. wbfdm/tests/models/test_instrument_prices.py +306 -0
  254. wbfdm/tests/models/test_instruments.py +202 -0
  255. wbfdm/tests/models/test_merge.py +99 -0
  256. wbfdm/tests/models/test_options.py +69 -0
  257. wbfdm/tests/test_tasks.py +6 -0
  258. wbfdm/tests/tests.py +10 -0
  259. wbfdm/urls.py +222 -0
  260. wbfdm/utils.py +54 -0
  261. wbfdm/viewsets/__init__.py +10 -0
  262. wbfdm/viewsets/configs/__init__.py +5 -0
  263. wbfdm/viewsets/configs/buttons/__init__.py +8 -0
  264. wbfdm/viewsets/configs/buttons/classifications.py +23 -0
  265. wbfdm/viewsets/configs/buttons/exchanges.py +9 -0
  266. wbfdm/viewsets/configs/buttons/instrument_prices.py +49 -0
  267. wbfdm/viewsets/configs/buttons/instruments.py +283 -0
  268. wbfdm/viewsets/configs/display/__init__.py +22 -0
  269. wbfdm/viewsets/configs/display/classifications.py +138 -0
  270. wbfdm/viewsets/configs/display/esg.py +75 -0
  271. wbfdm/viewsets/configs/display/exchanges.py +42 -0
  272. wbfdm/viewsets/configs/display/instrument_lists.py +137 -0
  273. wbfdm/viewsets/configs/display/instrument_prices.py +199 -0
  274. wbfdm/viewsets/configs/display/instrument_requests.py +116 -0
  275. wbfdm/viewsets/configs/display/instruments.py +618 -0
  276. wbfdm/viewsets/configs/display/instruments_relationships.py +65 -0
  277. wbfdm/viewsets/configs/display/monthly_performances.py +72 -0
  278. wbfdm/viewsets/configs/display/officers.py +16 -0
  279. wbfdm/viewsets/configs/display/prices.py +21 -0
  280. wbfdm/viewsets/configs/display/statement_with_estimates.py +101 -0
  281. wbfdm/viewsets/configs/display/statements.py +48 -0
  282. wbfdm/viewsets/configs/endpoints/__init__.py +41 -0
  283. wbfdm/viewsets/configs/endpoints/classifications.py +87 -0
  284. wbfdm/viewsets/configs/endpoints/esg.py +20 -0
  285. wbfdm/viewsets/configs/endpoints/exchanges.py +6 -0
  286. wbfdm/viewsets/configs/endpoints/financials_analysis.py +65 -0
  287. wbfdm/viewsets/configs/endpoints/instrument_lists.py +38 -0
  288. wbfdm/viewsets/configs/endpoints/instrument_prices.py +51 -0
  289. wbfdm/viewsets/configs/endpoints/instrument_requests.py +20 -0
  290. wbfdm/viewsets/configs/endpoints/instruments.py +13 -0
  291. wbfdm/viewsets/configs/endpoints/instruments_relationships.py +31 -0
  292. wbfdm/viewsets/configs/endpoints/statements.py +6 -0
  293. wbfdm/viewsets/configs/menus/__init__.py +9 -0
  294. wbfdm/viewsets/configs/menus/classifications.py +19 -0
  295. wbfdm/viewsets/configs/menus/exchanges.py +10 -0
  296. wbfdm/viewsets/configs/menus/instrument_lists.py +10 -0
  297. wbfdm/viewsets/configs/menus/instruments.py +20 -0
  298. wbfdm/viewsets/configs/menus/instruments_relationships.py +33 -0
  299. wbfdm/viewsets/configs/titles/__init__.py +42 -0
  300. wbfdm/viewsets/configs/titles/classifications.py +79 -0
  301. wbfdm/viewsets/configs/titles/esg.py +11 -0
  302. wbfdm/viewsets/configs/titles/exchanges.py +12 -0
  303. wbfdm/viewsets/configs/titles/financial_ratio_analysis.py +6 -0
  304. wbfdm/viewsets/configs/titles/financials_analysis.py +50 -0
  305. wbfdm/viewsets/configs/titles/instrument_prices.py +50 -0
  306. wbfdm/viewsets/configs/titles/instrument_requests.py +16 -0
  307. wbfdm/viewsets/configs/titles/instruments.py +31 -0
  308. wbfdm/viewsets/configs/titles/instruments_relationships.py +21 -0
  309. wbfdm/viewsets/configs/titles/market_data.py +13 -0
  310. wbfdm/viewsets/configs/titles/prices.py +15 -0
  311. wbfdm/viewsets/configs/titles/statement_with_estimates.py +10 -0
  312. wbfdm/viewsets/esg.py +72 -0
  313. wbfdm/viewsets/exchanges.py +63 -0
  314. wbfdm/viewsets/financial_analysis/__init__.py +3 -0
  315. wbfdm/viewsets/financial_analysis/financial_metric_analysis.py +85 -0
  316. wbfdm/viewsets/financial_analysis/financial_ratio_analysis.py +85 -0
  317. wbfdm/viewsets/financial_analysis/statement_with_estimates.py +145 -0
  318. wbfdm/viewsets/instruments/__init__.py +80 -0
  319. wbfdm/viewsets/instruments/classifications.py +279 -0
  320. wbfdm/viewsets/instruments/financials_analysis.py +614 -0
  321. wbfdm/viewsets/instruments/instrument_lists.py +77 -0
  322. wbfdm/viewsets/instruments/instrument_prices.py +542 -0
  323. wbfdm/viewsets/instruments/instrument_requests.py +51 -0
  324. wbfdm/viewsets/instruments/instruments.py +106 -0
  325. wbfdm/viewsets/instruments/instruments_relationships.py +235 -0
  326. wbfdm/viewsets/instruments/utils.py +27 -0
  327. wbfdm/viewsets/market_data.py +172 -0
  328. wbfdm/viewsets/mixins.py +9 -0
  329. wbfdm/viewsets/officers.py +27 -0
  330. wbfdm/viewsets/prices.py +62 -0
  331. wbfdm/viewsets/statements/__init__.py +1 -0
  332. wbfdm/viewsets/statements/statements.py +100 -0
  333. wbfdm/viewsets/technical_analysis/__init__.py +1 -0
  334. wbfdm/viewsets/technical_analysis/monthly_performances.py +93 -0
  335. wbfdm-2.2.1.dist-info/METADATA +15 -0
  336. wbfdm-2.2.1.dist-info/RECORD +337 -0
  337. wbfdm-2.2.1.dist-info/WHEEL +5 -0
@@ -0,0 +1,53 @@
1
+ # Generated by Django 5.0.6 on 2024-07-04 07:38
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("currency", "0001_initial"),
10
+ ("geography", "0001_initial"),
11
+ ("io", "0007_alter_exportsource_query_params"),
12
+ ("tags", "0001_initial"),
13
+ ("wbfdm", "0022_instrument_cusip_option_open_interest_20d_and_more"),
14
+ ]
15
+
16
+ operations = [
17
+ migrations.AddConstraint(
18
+ model_name="instrument",
19
+ constraint=models.UniqueConstraint(
20
+ condition=models.Q(("is_security", True)), fields=("refinitiv_identifier_code",), name="unique_ric"
21
+ ),
22
+ ),
23
+ migrations.AddConstraint(
24
+ model_name="instrument",
25
+ constraint=models.UniqueConstraint(
26
+ condition=models.Q(("is_security", True)), fields=("refinitiv_mnemonic_code",), name="unique_rmc"
27
+ ),
28
+ ),
29
+ migrations.AddConstraint(
30
+ model_name="instrument",
31
+ constraint=models.UniqueConstraint(
32
+ condition=models.Q(("is_security", True)), fields=("isin",), name="unique_isin"
33
+ ),
34
+ ),
35
+ migrations.AddConstraint(
36
+ model_name="instrument",
37
+ constraint=models.UniqueConstraint(
38
+ condition=models.Q(("is_security", True)), fields=("sedol",), name="unique_sedol"
39
+ ),
40
+ ),
41
+ migrations.AddConstraint(
42
+ model_name="instrument",
43
+ constraint=models.UniqueConstraint(
44
+ condition=models.Q(("is_security", True)), fields=("valoren",), name="unique_valoren"
45
+ ),
46
+ ),
47
+ migrations.AddConstraint(
48
+ model_name="instrument",
49
+ constraint=models.UniqueConstraint(
50
+ condition=models.Q(("is_security", True)), fields=("cusip",), name="unique_cusip"
51
+ ),
52
+ ),
53
+ ]
@@ -0,0 +1,36 @@
1
+ # Generated by Django 5.0.6 on 2024-07-08 11:12
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ dependencies = [
8
+ ("wbfdm", "0023_instrument_unique_ric_instrument_unique_rmc_and_more"),
9
+ ]
10
+
11
+ operations = [
12
+ migrations.AddField(
13
+ model_name="option",
14
+ name="open_interest_10d",
15
+ field=models.FloatField(
16
+ blank=True, help_text="Option Open Interest (10D)", null=True, verbose_name="Open Interest 10D"
17
+ ),
18
+ ),
19
+ migrations.AddField(
20
+ model_name="option",
21
+ name="volume_10d",
22
+ field=models.FloatField(blank=True, help_text="Option Volume (10D)", null=True, verbose_name="Volume 10D"),
23
+ ),
24
+ migrations.AddField(
25
+ model_name="optionaggregate",
26
+ name="open_interest_10d",
27
+ field=models.FloatField(
28
+ blank=True, help_text="Option Open Interest (10D)", null=True, verbose_name="Open Interest 10D"
29
+ ),
30
+ ),
31
+ migrations.AddField(
32
+ model_name="optionaggregate",
33
+ name="volume_10d",
34
+ field=models.FloatField(blank=True, help_text="Option Volume (10D)", null=True, verbose_name="Volume 10D"),
35
+ ),
36
+ ]
@@ -0,0 +1,29 @@
1
+ # Generated by Django 5.0.8 on 2024-08-09 14:20
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ dependencies = [
8
+ ("currency", "0001_initial"),
9
+ ("geography", "0001_initial"),
10
+ ("io", "0007_alter_exportsource_query_params"),
11
+ ("tags", "0001_initial"),
12
+ ("wbfdm", "0024_option_open_interest_10d_option_volume_10d_and_more"),
13
+ ]
14
+
15
+ operations = [
16
+ migrations.AddField(
17
+ model_name="instrument",
18
+ name="is_primary",
19
+ field=models.BooleanField(blank=True, null=True),
20
+ ),
21
+ migrations.AddConstraint(
22
+ model_name="instrument",
23
+ constraint=models.UniqueConstraint(
24
+ condition=models.Q(("is_primary", True)),
25
+ fields=("parent", "is_primary"),
26
+ name="unique_instrument_primary",
27
+ ),
28
+ ),
29
+ ]
@@ -0,0 +1,30 @@
1
+ # Generated by Django 5.0.8 on 2024-09-25 09:37
2
+
3
+ from django.db import migrations, models
4
+ from wbcore.contrib.currency.models import Currency
5
+ from wbfdm.models import Instrument, InstrumentType
6
+
7
+
8
+ def create_cash_equivalents(apps, schema_editor):
9
+ for currency in Currency.objects.all():
10
+ Instrument.objects.create(
11
+ name=f"Cash Equivalent {currency.key}",
12
+ instrument_type=InstrumentType.CASHEQUIVALENT,
13
+ currency=currency,
14
+ is_cash_equivalent=True,
15
+ )
16
+
17
+
18
+ class Migration(migrations.Migration):
19
+ dependencies = [
20
+ ("wbfdm", "0025_instrument_is_primary_and_more"),
21
+ ]
22
+
23
+ operations = [
24
+ migrations.AddField(
25
+ model_name="instrument",
26
+ name="is_cash_equivalent",
27
+ field=models.BooleanField(default=False),
28
+ ),
29
+ migrations.RunPython(create_cash_equivalents),
30
+ ]
@@ -0,0 +1,100 @@
1
+ # Generated by Django 5.0.9 on 2024-10-11 08:34
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ dependencies = [
8
+ ("currency", "0001_initial"),
9
+ ("geography", "0001_initial"),
10
+ ("io", "0007_alter_exportsource_query_params"),
11
+ ("tags", "0001_initial"),
12
+ ("wbfdm", "0026_instrument_is_cash_equivalent"),
13
+ ]
14
+
15
+ operations = [
16
+ migrations.RemoveConstraint(
17
+ model_name="instrument",
18
+ name="unique_ric",
19
+ ),
20
+ migrations.RemoveConstraint(
21
+ model_name="instrument",
22
+ name="unique_rmc",
23
+ ),
24
+ migrations.RemoveConstraint(
25
+ model_name="instrument",
26
+ name="unique_isin",
27
+ ),
28
+ migrations.RemoveConstraint(
29
+ model_name="instrument",
30
+ name="unique_sedol",
31
+ ),
32
+ migrations.RemoveConstraint(
33
+ model_name="instrument",
34
+ name="unique_valoren",
35
+ ),
36
+ migrations.RemoveConstraint(
37
+ model_name="instrument",
38
+ name="unique_cusip",
39
+ ),
40
+ migrations.RemoveConstraint(
41
+ model_name="instrument",
42
+ name="unique_instrument_primary",
43
+ ),
44
+ migrations.AddConstraint(
45
+ model_name="instrument",
46
+ constraint=models.UniqueConstraint(
47
+ condition=models.Q(("is_security", True), ("delisted_date__isnull", True)),
48
+ fields=("refinitiv_identifier_code",),
49
+ name="unique_ric",
50
+ ),
51
+ ),
52
+ migrations.AddConstraint(
53
+ model_name="instrument",
54
+ constraint=models.UniqueConstraint(
55
+ condition=models.Q(("is_security", True), ("delisted_date__isnull", True)),
56
+ fields=("refinitiv_mnemonic_code",),
57
+ name="unique_rmc",
58
+ ),
59
+ ),
60
+ migrations.AddConstraint(
61
+ model_name="instrument",
62
+ constraint=models.UniqueConstraint(
63
+ condition=models.Q(("is_security", True), ("delisted_date__isnull", True)),
64
+ fields=("isin",),
65
+ name="unique_isin",
66
+ ),
67
+ ),
68
+ migrations.AddConstraint(
69
+ model_name="instrument",
70
+ constraint=models.UniqueConstraint(
71
+ condition=models.Q(("is_security", True), ("delisted_date__isnull", True)),
72
+ fields=("sedol",),
73
+ name="unique_sedol",
74
+ ),
75
+ ),
76
+ migrations.AddConstraint(
77
+ model_name="instrument",
78
+ constraint=models.UniqueConstraint(
79
+ condition=models.Q(("is_security", True), ("delisted_date__isnull", True)),
80
+ fields=("valoren",),
81
+ name="unique_valoren",
82
+ ),
83
+ ),
84
+ migrations.AddConstraint(
85
+ model_name="instrument",
86
+ constraint=models.UniqueConstraint(
87
+ condition=models.Q(("is_security", True), ("delisted_date__isnull", True)),
88
+ fields=("cusip",),
89
+ name="unique_cusip",
90
+ ),
91
+ ),
92
+ migrations.AddConstraint(
93
+ model_name="instrument",
94
+ constraint=models.UniqueConstraint(
95
+ condition=models.Q(("is_primary", True), ("is_managed", False)),
96
+ fields=("parent", "is_primary"),
97
+ name="unique_instrument_primary",
98
+ ),
99
+ ),
100
+ ]
File without changes
@@ -0,0 +1,4 @@
1
+ from .esg import *
2
+ from .exchanges import *
3
+ from .instruments import *
4
+ from .exchanges import *
@@ -0,0 +1 @@
1
+ from .controversies import *
@@ -0,0 +1,81 @@
1
+ from typing import Any, Self
2
+
3
+ from django.db import models
4
+ from wbfdm.enums import (
5
+ ESGControveryFlag,
6
+ ESGControverySeverity,
7
+ ESGControveryStatus,
8
+ ESGControveryType,
9
+ )
10
+ from wbfdm.models.instruments.instruments import Instrument
11
+
12
+
13
+ class Controversy(models.Model):
14
+ external_id = models.CharField(max_length=512, unique=True)
15
+ instrument = models.ForeignKey("wbfdm.Instrument", on_delete=models.CASCADE, limit_choices_to=models.Q(level=0))
16
+ headline = models.TextField(verbose_name="Headline")
17
+ description = models.TextField(verbose_name="Description")
18
+ source = models.TextField(
19
+ verbose_name="Source", null=True, blank=True
20
+ ) # Source is usually a sentence but can be a whole text
21
+ status = models.CharField(
22
+ max_length=64,
23
+ choices=ESGControveryStatus.choices,
24
+ verbose_name="Status",
25
+ default=ESGControveryStatus.ONGOING.value,
26
+ null=True,
27
+ blank=True,
28
+ )
29
+ type = models.CharField(
30
+ max_length=64,
31
+ choices=ESGControveryType.choices,
32
+ verbose_name="Type",
33
+ default=ESGControveryType.STRUCTURAL.value,
34
+ null=True,
35
+ blank=True,
36
+ )
37
+ severity = models.CharField(
38
+ max_length=64,
39
+ choices=ESGControverySeverity.choices,
40
+ verbose_name="Severity",
41
+ default=ESGControverySeverity.MINOR.value,
42
+ null=True,
43
+ blank=True,
44
+ )
45
+ flag = models.CharField(
46
+ max_length=64,
47
+ choices=ESGControveryFlag.choices,
48
+ verbose_name="Flag",
49
+ default=ESGControveryFlag.GREEN.value,
50
+ null=True,
51
+ blank=True,
52
+ )
53
+ direct_involvement = models.BooleanField(default=True, verbose_name="Direct Involvement")
54
+ company_response = models.CharField(max_length=512, null=True, blank=True)
55
+
56
+ review = models.DateField(verbose_name="Reviewed")
57
+ initiated = models.DateField(verbose_name="initiated", null=True, blank=True)
58
+
59
+ @classmethod
60
+ def sync_from_dataloader(cls, controversy: dict[str, Any]) -> Self:
61
+ instrument = Instrument.objects.get(
62
+ pk=controversy["instrument_id"]
63
+ ).get_root() # we link it only to root level
64
+
65
+ return cls.objects.update_or_create(
66
+ external_id=controversy["id"],
67
+ defaults={
68
+ "instrument": instrument,
69
+ "headline": controversy["headline"],
70
+ "description": controversy["narrative"],
71
+ "source": controversy["source"],
72
+ "direct_involvement": controversy.get("direct_involvement", True),
73
+ "company_response": controversy["response"],
74
+ "review": controversy["review"],
75
+ "initiated": controversy["initiated"],
76
+ "flag": controversy["flag"],
77
+ "status": controversy["status"],
78
+ "type": controversy["type"],
79
+ "severity": controversy["assessment"],
80
+ },
81
+ )[0]
@@ -0,0 +1 @@
1
+ from .exchanges import Exchange
@@ -0,0 +1,223 @@
1
+ from django.contrib.postgres.fields import ArrayField
2
+ from django.db import models
3
+ from django.db.models import Q
4
+ from django.utils.translation import gettext_lazy as _
5
+ from wbcore.models import WBModel
6
+
7
+
8
+ class ExchangeManager(models.Manager):
9
+ def get_by_bbg(self, bbg):
10
+ """
11
+ If passed in a bbg ticker, we don't know whether it is a normal exchange code or a composite code.
12
+ Therefore we return either the exchange where this code is the bbg_exchange_codes, or the exchange where this composite code
13
+ exists, but only if it is the primary exchange
14
+ """
15
+ return self.get(Q(bbg_exchange_codes__contains=[bbg]) | (Q(bbg_composite=bbg) & Q(bbg_composite_primary=True)))
16
+
17
+
18
+ class Exchange(WBModel):
19
+ source_id = models.CharField(max_length=64, null=True, blank=True)
20
+ source = models.CharField(max_length=64, null=True, blank=True)
21
+ name = models.CharField(
22
+ max_length=265, null=True, blank=True, verbose_name="Exchange Name", help_text="Name of the Exchange."
23
+ )
24
+
25
+ opening_time = models.TimeField(
26
+ blank=True, null=True, verbose_name="Exchange Opening time", help_text="The opening time of the exchange"
27
+ )
28
+ closing_time = models.TimeField(
29
+ blank=True, null=True, verbose_name="Exchange Closing time", help_text="The closing time of the exchange"
30
+ )
31
+
32
+ mic_code = models.CharField(
33
+ max_length=4,
34
+ null=True,
35
+ blank=True,
36
+ verbose_name="MIC (ISO)",
37
+ unique=True,
38
+ help_text="Market Identifier Code.",
39
+ )
40
+ mic_name = models.CharField(
41
+ max_length=126,
42
+ null=True,
43
+ blank=True,
44
+ verbose_name="MIC (ISO) Name",
45
+ help_text="Market Identifier Name.",
46
+ )
47
+ operating_mic_code = models.CharField(
48
+ max_length=4,
49
+ null=True,
50
+ blank=True,
51
+ verbose_name="Operating MIC Code",
52
+ help_text="Operating Market Identifier Code.",
53
+ )
54
+ operating_mic_name = models.CharField(
55
+ max_length=126,
56
+ null=True,
57
+ blank=True,
58
+ verbose_name="Operating MIC Name",
59
+ help_text="Operating Market Identifier Name.",
60
+ )
61
+ ########################################################
62
+ # Bloomberg #
63
+ ########################################################
64
+
65
+ bbg_exchange_codes = ArrayField(
66
+ models.CharField(
67
+ max_length=4,
68
+ ),
69
+ blank=True,
70
+ default=list,
71
+ verbose_name="BBG Exchange Code",
72
+ help_text="Bloomberg Exchange Code.",
73
+ )
74
+
75
+ bbg_composite_primary = models.BooleanField(
76
+ default=False,
77
+ verbose_name="BBG Primary composite",
78
+ help_text="Indicates the primary exchange for this BBG Composite Code.",
79
+ )
80
+
81
+ bbg_composite = models.CharField(
82
+ max_length=4,
83
+ null=True,
84
+ blank=True,
85
+ verbose_name="BBG Composite Code",
86
+ help_text="Bloomberg Composite Code.",
87
+ )
88
+
89
+ ########################################################
90
+ # Reuters/Refinitiv #
91
+ ########################################################
92
+
93
+ refinitiv_identifier_code = models.CharField(
94
+ max_length=4,
95
+ null=True,
96
+ blank=True,
97
+ verbose_name="RIC Exchange Code",
98
+ help_text="Reuters Exchange Code.",
99
+ )
100
+ refinitiv_mnemonic = models.CharField(
101
+ max_length=4,
102
+ null=True,
103
+ blank=True,
104
+ verbose_name="Refinitiv Mnemonic",
105
+ help_text="Reuters Exchange Mnemonic Code.",
106
+ )
107
+ ########################################################
108
+ # INFORMATION #
109
+ ########################################################
110
+
111
+ country = models.ForeignKey(
112
+ to="geography.Geography",
113
+ null=True,
114
+ blank=True,
115
+ limit_choices_to={"level": 1},
116
+ on_delete=models.SET_NULL,
117
+ )
118
+
119
+ city = models.ForeignKey(
120
+ to="geography.Geography",
121
+ max_length=255,
122
+ null=True,
123
+ blank=True,
124
+ limit_choices_to={"level": 3},
125
+ related_name="city_exchanges",
126
+ on_delete=models.SET_NULL,
127
+ verbose_name="City",
128
+ help_text="The city where this Exchange is located at.",
129
+ )
130
+
131
+ city = models.ForeignKey(
132
+ "geography.Geography",
133
+ null=True,
134
+ blank=True,
135
+ related_name="exchanges",
136
+ verbose_name="City",
137
+ on_delete=models.PROTECT,
138
+ help_text="The city where this Exchange is located at.",
139
+ limit_choices_to={"level": 3},
140
+ )
141
+
142
+ website = models.URLField(
143
+ null=True,
144
+ blank=True,
145
+ verbose_name="Website",
146
+ help_text="The Website of the Exchange",
147
+ )
148
+
149
+ comments = models.TextField(
150
+ default="",
151
+ blank=True,
152
+ verbose_name="Comments",
153
+ help_text="Any comments for this exchange",
154
+ )
155
+
156
+ last_updated = models.DateTimeField(auto_now=True)
157
+
158
+ objects = ExchangeManager()
159
+
160
+ @property
161
+ def identifier_repr(self) -> str:
162
+ if self.bbg_exchange_codes:
163
+ return self.bbg_exchange_codes[0]
164
+ if self.mic_code:
165
+ return self.mic_code
166
+ if self.operating_mic_code:
167
+ return self.operating_mic_code
168
+ if self.bbg_composite:
169
+ return self.bbg_composite
170
+ if self.refinitiv_identifier_code:
171
+ return self.refinitiv_identifier_code
172
+ if self.refinitiv_mnemonic:
173
+ return self.refinitiv_mnemonic
174
+
175
+ def __str__(self) -> str:
176
+ repr = self.identifier_repr
177
+ if self.name:
178
+ repr = f"{self.name} ({self.identifier_repr})"
179
+ elif self.mic_name:
180
+ repr = f"{self.mic_name} ({self.identifier_repr})"
181
+ return repr
182
+
183
+ class Meta:
184
+ verbose_name = _("Exchange")
185
+ verbose_name_plural = _("Exchanges")
186
+ indexes = [models.Index(fields=["source_id", "source"])]
187
+ constraints = [
188
+ models.UniqueConstraint(fields=["source", "source_id"], name="unique_exchange_source"),
189
+ ]
190
+
191
+ @classmethod
192
+ def dict_to_model(cls, exchange_data):
193
+ if isinstance(exchange_data, int):
194
+ return Exchange.objects.filter(id=exchange_data).first()
195
+ elif (bbg_code := exchange_data.get("bbg_exchange_codes", None)) or (
196
+ bbg_code := exchange_data.get("bbg_exchange_code", None)
197
+ ):
198
+ return Exchange.objects.filter(bbg_exchange_codes__contains=[bbg_code]).first()
199
+ elif mic_code := exchange_data.pop("exchange__mic_code", None):
200
+ return Exchange.objects.filter(mic_code=mic_code).first()
201
+ elif bbg_composite := exchange_data.pop("exchange__bbg_composite", None):
202
+ return Exchange.objects.filter(bbg_composite=bbg_composite).first()
203
+
204
+ @classmethod
205
+ def get_endpoint_basename(cls):
206
+ return "wbfdm:exchange"
207
+
208
+ @classmethod
209
+ def get_representation_endpoint(cls):
210
+ return "wbfdm:exchangerepresentation-list"
211
+
212
+ @classmethod
213
+ def get_representation_value_key(cls):
214
+ return "id"
215
+
216
+ @classmethod
217
+ def get_representation_label_key(cls):
218
+ return "{{name}} ({{mic_code}})"
219
+
220
+ @property
221
+ def time_zone(self):
222
+ if self.city:
223
+ return self.city.time_zone
wbfdm/models/fields.py ADDED
@@ -0,0 +1,117 @@
1
+ import datetime
2
+ import decimal
3
+ import json
4
+ import uuid
5
+
6
+ from django.db import DEFAULT_DB_ALIAS, models
7
+ from django.db.models.sql.where import AND, WhereNode
8
+ from django.utils.duration import duration_iso_string
9
+ from django.utils.functional import Promise
10
+ from django.utils.timezone import is_aware
11
+
12
+
13
+ class CompositeKey(models.AutoField):
14
+ class Key(dict):
15
+ """Dictionary with json-compatible string conversion."""
16
+
17
+ def __str__(self):
18
+ return json.dumps(self)
19
+
20
+ def __hash__(self):
21
+ return hash(tuple(self[key] for key in sorted(self.keys())))
22
+
23
+ def __init__(self, columns: list[str], db_column_ref: str | None = None, *args, **kwargs):
24
+ self.columns = columns
25
+ self.db_column_ref = db_column_ref
26
+ super().__init__(primary_key=True, *args, **kwargs)
27
+
28
+ def contribute_to_class(self, cls, name, private_only=False):
29
+ self.set_attributes_from_name(name)
30
+ self.model = cls
31
+ self.concrete = False
32
+ self.editable = False
33
+ if self.db_column_ref:
34
+ self.column = self.db_column_ref
35
+ else:
36
+ self.column = self.model._meta.get_field(self.columns[1]).db_column # for default order_by
37
+ cls._meta.add_field(self, private=True) # virtual field
38
+ cls._meta.setup_pk(self) # acts as pk
39
+
40
+ if not getattr(cls, self.attname, None):
41
+ setattr(cls, self.attname, self)
42
+
43
+ def get_prep_value(self, value):
44
+ return self.to_python(value)
45
+
46
+ def to_python(self, value):
47
+ if value is None or isinstance(value, CompositeKey.Key):
48
+ return value
49
+ if isinstance(value, dict):
50
+ return CompositeKey.Key(value)
51
+ return CompositeKey.Key(json.loads(value))
52
+
53
+ def to_json(self, value):
54
+ if isinstance(value, datetime.datetime):
55
+ result = value.isoformat()
56
+ if value.microsecond:
57
+ result = result[:23] + result[26:]
58
+ if result.endswith("+00:00"):
59
+ result = result[:-6] + "Z"
60
+ return result
61
+ elif isinstance(value, datetime.date):
62
+ return value.isoformat()
63
+ elif isinstance(value, datetime.time):
64
+ if is_aware(value):
65
+ raise ValueError("JSON can't represent timezone-aware times.")
66
+ result = value.isoformat()
67
+ if value.microsecond:
68
+ result = result[:12]
69
+ return result
70
+ elif isinstance(value, datetime.timedelta):
71
+ return duration_iso_string(value)
72
+ elif isinstance(value, (decimal.Decimal, uuid.UUID, Promise)):
73
+ return str(value)
74
+ return value
75
+
76
+ def bulk_related_objects(self, objs, using=DEFAULT_DB_ALIAS):
77
+ return []
78
+
79
+ def __get__(self, instance, cls=None):
80
+ if instance is None:
81
+ return self
82
+
83
+ return CompositeKey.Key(
84
+ {
85
+ column: self.to_json(self.model._meta.get_field(column).value_from_object(instance))
86
+ for column in self.columns
87
+ }
88
+ )
89
+
90
+ def __set__(self, instance, value):
91
+ """
92
+ I hope it's safe to ignore!
93
+ """
94
+ pass
95
+
96
+
97
+ @CompositeKey.register_lookup
98
+ class Exact(models.Lookup):
99
+ lookup_name = "exact"
100
+
101
+ def __init__(self, *args, **kwargs):
102
+ super().__init__(*args, **kwargs)
103
+
104
+ def as_sql(self, compiler, connection):
105
+ fields = [self.lhs.field.model._meta.get_field(column) for column in self.lhs.field.columns]
106
+
107
+ lookup_classes = [field.get_lookup("exact") for field in fields]
108
+
109
+ lookups = [
110
+ lookup_class(field.get_col(self.lhs.alias), self.rhs[column])
111
+ for lookup_class, field, column in zip(lookup_classes, fields, self.lhs.field.columns)
112
+ ]
113
+
114
+ value_constraint = WhereNode()
115
+ for lookup in lookups:
116
+ value_constraint.add(lookup, AND)
117
+ return value_constraint.as_sql(compiler, connection)