aa-ledger 1.0.4__py3-none-any.whl → 2.0.0__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.
Files changed (280) hide show
  1. {aa_ledger-1.0.4.dist-info → aa_ledger-2.0.0.dist-info}/METADATA +5 -6
  2. aa_ledger-2.0.0.dist-info/RECORD +267 -0
  3. ledger/__init__.py +2 -2
  4. ledger/admin.py +23 -18
  5. ledger/api/__init__.py +23 -7
  6. ledger/api/{ledger/admin.py → admin.py} +25 -31
  7. ledger/api/alliance.py +755 -0
  8. ledger/api/character.py +786 -0
  9. ledger/api/corporation.py +1141 -0
  10. ledger/api/{helpers.py → helpers/core.py} +33 -33
  11. ledger/api/helpers/icons.py +372 -0
  12. ledger/api/helpers/planetary_helper.py +354 -0
  13. ledger/api/planetary.py +354 -0
  14. ledger/api/schema.py +240 -15
  15. ledger/app_settings.py +11 -27
  16. ledger/auth_hooks.py +2 -2
  17. ledger/constants.py +50 -177
  18. ledger/decorators.py +2 -46
  19. ledger/forms.py +133 -39
  20. ledger/helpers/billboard.py +194 -144
  21. ledger/helpers/cache.py +105 -0
  22. ledger/helpers/discord.py +2 -4
  23. ledger/helpers/eveonline.py +160 -0
  24. ledger/helpers/ledger_data.py +23 -0
  25. ledger/helpers/ref_type.py +53 -78
  26. ledger/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  27. ledger/locale/cs_CZ/LC_MESSAGES/django.po +349 -193
  28. ledger/locale/de/LC_MESSAGES/django.mo +0 -0
  29. ledger/locale/de/LC_MESSAGES/django.po +528 -379
  30. ledger/locale/django.pot +721 -546
  31. ledger/locale/es/LC_MESSAGES/django.mo +0 -0
  32. ledger/locale/es/LC_MESSAGES/django.po +349 -194
  33. ledger/locale/fr_FR/LC_MESSAGES/django.mo +0 -0
  34. ledger/locale/fr_FR/LC_MESSAGES/django.po +349 -193
  35. ledger/locale/it_IT/LC_MESSAGES/django.mo +0 -0
  36. ledger/locale/it_IT/LC_MESSAGES/django.po +349 -193
  37. ledger/locale/ja/LC_MESSAGES/django.mo +0 -0
  38. ledger/locale/ja/LC_MESSAGES/django.po +348 -193
  39. ledger/locale/ko_KR/LC_MESSAGES/django.mo +0 -0
  40. ledger/locale/ko_KR/LC_MESSAGES/django.po +349 -193
  41. ledger/locale/nl_NL/LC_MESSAGES/django.mo +0 -0
  42. ledger/locale/nl_NL/LC_MESSAGES/django.po +349 -193
  43. ledger/locale/pl_PL/LC_MESSAGES/django.mo +0 -0
  44. ledger/locale/pl_PL/LC_MESSAGES/django.po +350 -193
  45. ledger/locale/ru/LC_MESSAGES/django.mo +0 -0
  46. ledger/locale/ru/LC_MESSAGES/django.po +348 -193
  47. ledger/locale/sk/LC_MESSAGES/django.mo +0 -0
  48. ledger/locale/sk/LC_MESSAGES/django.po +348 -193
  49. ledger/locale/uk/LC_MESSAGES/django.mo +0 -0
  50. ledger/locale/uk/LC_MESSAGES/django.po +348 -193
  51. ledger/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
  52. ledger/locale/zh_Hans/LC_MESSAGES/django.po +348 -193
  53. ledger/managers/character_audit_manager.py +28 -20
  54. ledger/managers/character_journal_manager.py +185 -357
  55. ledger/managers/character_mining_manager.py +52 -26
  56. ledger/managers/character_planetary_manager.py +178 -136
  57. ledger/managers/corporation_audit_manager.py +36 -27
  58. ledger/managers/corporation_journal_manager.py +92 -56
  59. ledger/managers/general_manager.py +8 -7
  60. ledger/migrations/0018_remove_characterplanet_ledger_char_planet__58a5b6_idx_and_more.py +44 -0
  61. ledger/migrations/0019_rename_characteraudit_characterowner_and_more.py +48 -0
  62. ledger/models/__init__.py +5 -11
  63. ledger/models/characteraudit.py +101 -109
  64. ledger/models/corporationaudit.py +94 -49
  65. ledger/models/general.py +105 -211
  66. ledger/models/helpers/update_manager.py +302 -0
  67. ledger/models/planetary.py +60 -205
  68. ledger/providers.py +101 -0
  69. ledger/static/ledger/css/{ledger.css → aa-ledger.css} +54 -28
  70. ledger/static/ledger/js/aa-ledger.js +124 -0
  71. ledger/static/ledger/js/charts.js +25 -1
  72. ledger/static/ledger/js/view-alliance-ledger.js +383 -0
  73. ledger/static/ledger/js/view-character-ledger.js +388 -0
  74. ledger/static/ledger/js/view-corporation-ledger.js +402 -0
  75. ledger/static/ledger/js/view-planetary.js +492 -0
  76. ledger/static/ledger/libs/amCharts/5.14.4/js/flow.js +2 -0
  77. ledger/static/ledger/libs/amCharts/5.14.4/js/index.js +2 -0
  78. ledger/static/ledger/libs/amCharts/5.14.4/js/percent.js +2 -0
  79. ledger/static/ledger/libs/amCharts/5.14.4/js/themes/Animated.js +2 -0
  80. ledger/static/ledger/libs/amCharts/5.14.4/js/themes/Dark.js +2 -0
  81. ledger/static/ledger/libs/amCharts/5.14.4/js/xy.js +2 -0
  82. ledger/static/ledger/libs/datatables/2.3.5/css/dataTables.bootstrap5.css +610 -0
  83. ledger/static/ledger/libs/datatables/2.3.5/js/dataTables.bootstrap5.js +122 -0
  84. ledger/static/ledger/libs/datatables/2.3.5/js/dataTables.js +14127 -0
  85. ledger/static/ledger/libs/datatables/Extensions/ColumnControl/1.1.1/css/columnControl.bootstrap5.css +516 -0
  86. ledger/static/ledger/libs/datatables/Extensions/ColumnControl/1.1.1/css/columnControl.dataTables.css +529 -0
  87. ledger/static/ledger/libs/datatables/Extensions/ColumnControl/1.1.1/js/columnControl.bootstrap5.js +73 -0
  88. ledger/static/ledger/libs/datatables/Extensions/ColumnControl/1.1.1/js/dataTables.columnControl.js +3090 -0
  89. ledger/static/ledger/libs/datatables/Extensions/FixedHeader/4.0.4/css/fixedHeader.bootstrap5.css +20 -0
  90. ledger/static/ledger/libs/datatables/Extensions/FixedHeader/4.0.4/js/dataTables.fixedHeader.js +1203 -0
  91. ledger/static/ledger/libs/datatables/Extensions/FixedHeader/4.0.4/js/fixedHeader.bootstrap5.js +59 -0
  92. ledger/tasks.py +157 -141
  93. ledger/templates/ledger/base.html +59 -21
  94. ledger/templates/ledger/bundles/aa-ledger-css.html +3 -0
  95. ledger/templates/ledger/bundles/aa-ledger-js.html +3 -0
  96. ledger/templates/ledger/bundles/view-alliance-ledger-js.html +14 -0
  97. ledger/templates/ledger/bundles/view-character-ledger-js.html +15 -0
  98. ledger/templates/ledger/bundles/view-character-planetary-css.html +3 -0
  99. ledger/templates/ledger/bundles/view-character-planetary-js.html +4 -0
  100. ledger/templates/ledger/bundles/view-corporation-ledger-js.html +15 -0
  101. ledger/templates/ledger/partials/modal/confirm.html +0 -1
  102. ledger/templates/ledger/partials/modal/request-accept-delete-alliance.html +38 -0
  103. ledger/templates/ledger/partials/modal/request-accept-delete-character.html +38 -0
  104. ledger/templates/ledger/partials/modal/request-accept-delete-corporation.html +38 -0
  105. ledger/templates/ledger/partials/modal/request-accept-switch-notification.html +38 -0
  106. ledger/templates/ledger/partials/modal/request-view-alliance-details.html +26 -0
  107. ledger/templates/ledger/partials/modal/request-view-character-details.html +26 -0
  108. ledger/templates/ledger/partials/modal/request-view-corporation-details.html +26 -0
  109. ledger/templates/ledger/partials/modal/request-view-extractor.html +32 -0
  110. ledger/templates/ledger/partials/modal/request-view-factory.html +31 -0
  111. ledger/templates/ledger/partials/{menu → navigation}/administration.html +8 -0
  112. ledger/templates/ledger/partials/{menu → navigation}/navigation.html +2 -2
  113. ledger/templates/ledger/partials/{administration → view-alliance-administration}/alliance_corporations.html +3 -3
  114. ledger/templates/ledger/partials/view-alliance-administration/dashboard.html +81 -0
  115. ledger/templates/ledger/partials/view-alliance-ledger/alliance-billboard.html +25 -0
  116. ledger/templates/ledger/partials/view-alliance-ledger/alliance-ledger-details.html +21 -0
  117. ledger/templates/ledger/partials/view-alliance-ledger/alliance-table.html +24 -0
  118. ledger/templates/ledger/partials/view-alliance-ledger/information/daily.html +18 -0
  119. ledger/templates/ledger/partials/view-alliance-ledger/information/hourly.html +18 -0
  120. ledger/templates/ledger/partials/view-alliance-ledger/information/summary.html +19 -0
  121. ledger/templates/ledger/partials/{administration → view-character-administration}/character.html +1 -9
  122. ledger/templates/ledger/partials/{administration → view-character-administration}/dashboard.html +0 -34
  123. ledger/templates/ledger/partials/view-character-ledger/character-billboard.html +25 -0
  124. ledger/templates/ledger/partials/view-character-ledger/character-ledger-details.html +21 -0
  125. ledger/templates/ledger/partials/view-character-ledger/character-table.html +25 -0
  126. ledger/templates/ledger/partials/view-character-ledger/information/daily.html +18 -0
  127. ledger/templates/ledger/partials/view-character-ledger/information/hourly.html +18 -0
  128. ledger/templates/ledger/partials/view-character-ledger/information/summary.html +19 -0
  129. ledger/templates/ledger/partials/view-character-planetary/extractor-table.html +24 -0
  130. ledger/templates/ledger/partials/view-character-planetary/factory-table.html +24 -0
  131. ledger/templates/ledger/partials/view-character-planetary/planetary-table.html +22 -0
  132. ledger/templates/ledger/partials/view-character-planetary/storage-table.html +23 -0
  133. ledger/templates/ledger/partials/{administration → view-corporation-administration}/corporation.html +5 -13
  134. ledger/templates/ledger/partials/{administration → view-corporation-administration}/corporation_characters.html +1 -1
  135. ledger/templates/ledger/partials/view-corporation-administration/dashboard.html +81 -0
  136. ledger/templates/ledger/partials/view-corporation-ledger/corporation-billboard.html +25 -0
  137. ledger/templates/ledger/partials/view-corporation-ledger/corporation-ledger-details.html +21 -0
  138. ledger/templates/ledger/partials/view-corporation-ledger/corporation-table.html +26 -0
  139. ledger/templates/ledger/partials/view-corporation-ledger/information/daily.html +18 -0
  140. ledger/templates/ledger/partials/view-corporation-ledger/information/hourly.html +18 -0
  141. ledger/templates/ledger/partials/view-corporation-ledger/information/summary.html +19 -0
  142. ledger/templates/ledger/view-administration.html +62 -0
  143. ledger/templates/ledger/view-alliance-administration.html +49 -0
  144. ledger/templates/ledger/view-alliance-ledger.html +72 -0
  145. ledger/templates/ledger/view-alliance-overview.html +131 -0
  146. ledger/templates/ledger/view-character-administration.html +42 -0
  147. ledger/templates/ledger/view-character-ledger.html +73 -0
  148. ledger/templates/ledger/view-character-overview.html +135 -0
  149. ledger/templates/ledger/view-character-planetary-overview.html +135 -0
  150. ledger/templates/ledger/view-character-planetary.html +73 -0
  151. ledger/templates/ledger/view-corporation-administration.html +42 -0
  152. ledger/templates/ledger/view-corporation-ledger.html +73 -0
  153. ledger/templates/ledger/view-corporation-overview.html +131 -0
  154. ledger/templatetags/ledger.py +3 -5
  155. ledger/tests/__init__.py +187 -0
  156. ledger/tests/test_admin.py +164 -68
  157. ledger/tests/test_auth_hook.py +31 -13
  158. ledger/tests/test_decarators.py +14 -79
  159. ledger/tests/test_discord_installed.py +0 -1
  160. ledger/tests/test_helpers/test_ledger_data.py +19 -0
  161. ledger/tests/test_managers/test_character_audit_manager.py +111 -69
  162. ledger/tests/test_managers/test_character_journal_manager.py +48 -208
  163. ledger/tests/test_managers/test_character_mining_manager.py +37 -16
  164. ledger/tests/test_managers/test_corporation_division_manager.py +66 -28
  165. ledger/tests/test_managers/test_corporation_journal_manager.py +39 -42
  166. ledger/tests/test_managers/test_general_manager.py +78 -18
  167. ledger/tests/test_managers/test_planetary_manager.py +73 -32
  168. ledger/tests/test_models/test_characteraudit.py +58 -74
  169. ledger/tests/test_models/test_characterminingledger.py +20 -26
  170. ledger/tests/test_models/test_characterwalletjournal.py +10 -33
  171. ledger/tests/test_models/test_corporationaudit.py +41 -35
  172. ledger/tests/test_models/test_corporationwalletjournal.py +35 -32
  173. ledger/tests/test_models/test_general.py +44 -11
  174. ledger/tests/test_models/test_planetary.py +14 -80
  175. ledger/tests/test_templatetags.py +2 -7
  176. ledger/tests/test_views/corporation/test_add_corp.py +16 -35
  177. ledger/tests/test_views/corporation/test_delete_corporation.py +66 -42
  178. ledger/tests/test_views/test_access.py +512 -545
  179. ledger/tests/test_views/test_add_ally.py +57 -46
  180. ledger/tests/test_views/test_add_char.py +21 -33
  181. ledger/tests/test_views/test_delete_character.py +24 -21
  182. ledger/tests/testdata/README_ESI_STUB.md +430 -0
  183. ledger/tests/testdata/esi_stub_openapi.py +511 -0
  184. ledger/tests/testdata/integrations/__init__.py +0 -0
  185. ledger/tests/testdata/{load_eveuniverse.py → integrations/eveuniverse.py} +0 -1
  186. ledger/tests/testdata/integrations/planetary.py +13 -0
  187. ledger/tests/testdata/json/factory.json +281 -0
  188. ledger/tests/testdata/json/inactive.json +281 -0
  189. ledger/tests/testdata/json/pins.json +175 -272
  190. ledger/tests/testdata/json/route.json +95 -528
  191. ledger/tests/testdata/test_esi_stub.py +468 -0
  192. ledger/tests/testdata/utils.py +601 -0
  193. ledger/thirdparty/charlink_hook.py +60 -30
  194. ledger/urls.py +0 -135
  195. ledger/views/alliance/add_ally.py +2 -4
  196. ledger/views/alliance/alliance_ledger.py +64 -147
  197. ledger/views/character/add_char.py +8 -10
  198. ledger/views/character/character_ledger.py +60 -126
  199. ledger/views/character/planetary.py +5 -98
  200. ledger/views/corporation/add_corp.py +10 -12
  201. ledger/views/corporation/corporation_ledger.py +65 -327
  202. ledger/views/index.py +92 -30
  203. aa_ledger-1.0.4.dist-info/RECORD +0 -236
  204. ledger/api/api_helper/planetary_helper.py +0 -107
  205. ledger/api/ledger/__init__.py +0 -7
  206. ledger/api/ledger/planetary.py +0 -231
  207. ledger/helpers/alliance.py +0 -317
  208. ledger/helpers/character.py +0 -251
  209. ledger/helpers/core.py +0 -665
  210. ledger/helpers/corporation.py +0 -427
  211. ledger/helpers/data_exporter.py +0 -452
  212. ledger/static/ledger/js/planetary-confirm.js +0 -66
  213. ledger/static/ledger/js/planetary.js +0 -143
  214. ledger/templates/ledger/admin.html +0 -43
  215. ledger/templates/ledger/allyledger/admin/alliance_administration.html +0 -46
  216. ledger/templates/ledger/allyledger/admin/alliance_overview.html +0 -108
  217. ledger/templates/ledger/allyledger/alliance_ledger.html +0 -86
  218. ledger/templates/ledger/bundles/character-ledger-bundles.html +0 -66
  219. ledger/templates/ledger/bundles/corporation-ledger-bundles.html +0 -75
  220. ledger/templates/ledger/bundles/ledger-bundles.html +0 -23
  221. ledger/templates/ledger/bundles/ledger-css.html +0 -3
  222. ledger/templates/ledger/bundles/planetary-bundles.html +0 -50
  223. ledger/templates/ledger/bundles/table-css.html +0 -3
  224. ledger/templates/ledger/charledger/admin/character_administration.html +0 -39
  225. ledger/templates/ledger/charledger/admin/character_overview.html +0 -106
  226. ledger/templates/ledger/charledger/character_ledger.html +0 -94
  227. ledger/templates/ledger/charledger/planetary/admin/planetary_overview.html +0 -123
  228. ledger/templates/ledger/charledger/planetary/planetary_ledger.html +0 -54
  229. ledger/templates/ledger/corpledger/admin/corporation_administration.html +0 -39
  230. ledger/templates/ledger/corpledger/admin/corporation_overview.html +0 -108
  231. ledger/templates/ledger/corpledger/corporation_ledger.html +0 -129
  232. ledger/templates/ledger/data-export.html +0 -78
  233. ledger/templates/ledger/error.html +0 -31
  234. ledger/templates/ledger/partials/form/error-message.html +0 -1
  235. ledger/templates/ledger/partials/information/daily.html +0 -56
  236. ledger/templates/ledger/partials/information/day.html +0 -48
  237. ledger/templates/ledger/partials/information/error.html +0 -8
  238. ledger/templates/ledger/partials/information/hourly.html +0 -53
  239. ledger/templates/ledger/partials/information/summary.html +0 -88
  240. ledger/templates/ledger/partials/information/view_character_content.html +0 -35
  241. ledger/templates/ledger/partials/modal/switchalarm_confirm.html +0 -39
  242. ledger/templates/ledger/partials/modal/view_extractor.html +0 -48
  243. ledger/templates/ledger/partials/modal/view_factory.html +0 -123
  244. ledger/templates/ledger/partials/table/char-ledger.html +0 -85
  245. ledger/templates/ledger/partials/table/corp-ledger.html +0 -66
  246. ledger/templates/ledger/partials/table/planetary.html +0 -18
  247. ledger/templates/ledger/partials/thirdparty/billboard.html +0 -22
  248. ledger/templates/ledger/partials/view/card.html +0 -160
  249. ledger/templates/ledger/permission.html +0 -2
  250. ledger/tests/test_helpers/test_billboard.py +0 -11
  251. ledger/tests/test_helpers/test_data_exporter.py +0 -207
  252. ledger/tests/test_tasks.py +0 -282
  253. ledger/tests/test_view_helpers/test_core.py +0 -47
  254. ledger/tests/test_views/corporation/test_corporation.py +0 -267
  255. ledger/tests/test_views/test_planetary.py +0 -137
  256. ledger/tests/testdata/esi_stub.py +0 -109
  257. ledger/tests/testdata/esi_stub_migration.py +0 -80
  258. ledger/tests/testdata/generate_characteraudit.py +0 -106
  259. ledger/tests/testdata/generate_corporationaudit.py +0 -74
  260. ledger/tests/testdata/generate_events.py +0 -31
  261. ledger/tests/testdata/generate_miningledger.py +0 -13
  262. ledger/tests/testdata/generate_planets.py +0 -48
  263. ledger/tests/testdata/generate_walletjournal.py +0 -42
  264. ledger/tests/testdata/json/czarno-pins.json +0 -240
  265. ledger/tests/testdata/json/czarno-routes.json +0 -165
  266. ledger/tests/testdata/json/pins2.json +0 -538
  267. {aa_ledger-1.0.4.dist-info → aa_ledger-2.0.0.dist-info}/WHEEL +0 -0
  268. {aa_ledger-1.0.4.dist-info → aa_ledger-2.0.0.dist-info}/licenses/LICENSE +0 -0
  269. /ledger/{tests/test_view_helpers → api/helpers}/__init__.py +0 -0
  270. /ledger/templates/ledger/bundles/{ally-administration-bundles.html → view-alliance-administration-js.html} +0 -0
  271. /ledger/templates/ledger/bundles/{char-administration-bundles.html → view-character-administration-js.html} +0 -0
  272. /ledger/templates/ledger/bundles/{corp-administration-bundles.html → view-corporation-administration-js.html} +0 -0
  273. /ledger/templates/ledger/partials/{administration → view-alliance-administration}/alliance.html +0 -0
  274. /ledger/tests/testdata/{esi.json → esi_test_data.json} +0 -0
  275. /ledger/tests/testdata/{allianceauth.json → integrations/allianceauth.json} +0 -0
  276. /ledger/tests/testdata/{load_allianceauth.py → integrations/allianceauth.py} +0 -0
  277. /ledger/tests/testdata/{eveentity.json → integrations/eveentity.json} +0 -0
  278. /ledger/tests/testdata/{load_eveentity.py → integrations/eveentity.py} +0 -0
  279. /ledger/tests/testdata/{eveuniverse.json → integrations/eveuniverse.json} +0 -0
  280. /ledger/tests/testdata/{planetary.json → integrations/planetary.json} +0 -0
@@ -0,0 +1,354 @@
1
+ # Third Party
2
+ from ninja import schema
3
+
4
+ # Django
5
+ from django.utils.html import format_html
6
+ from django.utils.translation import gettext_lazy as _
7
+
8
+ # Alliance Auth
9
+ from allianceauth.services.hooks import get_extension_logger
10
+
11
+ # Alliance Auth (External Libs)
12
+ from eveuniverse.models import EveType
13
+
14
+ # AA Ledger
15
+ from ledger import __title__
16
+ from ledger.helpers.eveonline import get_icon_render_url
17
+ from ledger.models.planetary import CharacterPlanetDetails
18
+ from ledger.providers import AppLogger
19
+
20
+ logger = AppLogger(get_extension_logger(__name__), __title__)
21
+
22
+
23
+ class ProductSchema(schema.Schema):
24
+ item_id: int
25
+ item_name: str
26
+ item_quantity: int | None = None
27
+ icon: str | None = None
28
+
29
+
30
+ class FactorySchema(schema.Schema):
31
+ factory_name: str
32
+ products: list[ProductSchema]
33
+ is_active: str
34
+
35
+
36
+ class ProduceSchema(schema.Schema):
37
+ factory_name: str
38
+ input_products: list[ProductSchema]
39
+ output_product: ProductSchema | None = None
40
+ is_active: str
41
+
42
+
43
+ class StorageSchema(schema.Schema):
44
+ factory_name: str
45
+ product: ProductSchema
46
+
47
+
48
+ def get_factories_info(planet_details: CharacterPlanetDetails) -> list[FactorySchema]:
49
+ """
50
+ Get the factories information for a planet.
51
+
52
+ Args:
53
+ planet_details (CharacterPlanetDetails): The planetary details object.
54
+ Returns:
55
+ list[FactorySchema]: A list with the factory information for the Planet.
56
+ """
57
+ response_factories_list: list[FactorySchema] = []
58
+
59
+ try:
60
+ factories = planet_details.factories.values()
61
+ except AttributeError:
62
+ return response_factories_list
63
+
64
+ for factory_info in factories:
65
+ factory_name = factory_info.get("facility_name") or _("No facility")
66
+ # Only process Processors
67
+ if factory_info.get("facility_type") != "Processors":
68
+ continue
69
+
70
+ # Skip if no ressources
71
+ ressources = factory_info.get("ressources", None) or None
72
+ if ressources is None:
73
+ continue
74
+
75
+ ressource_types = get_resource_type(ressources)
76
+ ressource_list = []
77
+
78
+ # Build Ressource List from Factories
79
+ for product in ressource_types.values():
80
+ product = ProductSchema(
81
+ item_id=product["item_id"],
82
+ item_name=product["item_name"],
83
+ icon=get_icon_render_url(
84
+ type_id=product["item_id"],
85
+ type_name=product["item_name"],
86
+ as_html=True,
87
+ ),
88
+ )
89
+ ressource_list.append(product)
90
+
91
+ # Populate Product if exists
92
+ if factory_info.get("output_product", None) is not None:
93
+ output_product = ProductSchema(
94
+ item_id=factory_info["output_product"]["item_id"],
95
+ item_name=factory_info["output_product"]["item_name"],
96
+ icon=get_icon_render_url(
97
+ type_id=factory_info["output_product"]["item_id"],
98
+ type_name=factory_info["output_product"]["item_name"],
99
+ as_html=True,
100
+ ),
101
+ )
102
+ ressource_list.append(output_product)
103
+
104
+ is_active = factory_info.get("is_active", False)
105
+
106
+ response_factories_list.append(
107
+ FactorySchema(
108
+ factory_name=factory_name,
109
+ products=ressource_list,
110
+ is_active=generate_is_active_icon(is_active),
111
+ )
112
+ )
113
+ return response_factories_list
114
+
115
+
116
+ def get_factory_info(planet_details: CharacterPlanetDetails) -> list[FactorySchema]:
117
+ """
118
+ Get the factory information for a planet.
119
+
120
+ Args:
121
+ planet_details (CharacterPlanetDetails): The planetary details object.
122
+ Returns:
123
+ list[FactorySchema]: A list with the factory information for the Planet.
124
+ """
125
+ response_factories_list: list[FactorySchema] = []
126
+
127
+ try:
128
+ factories = planet_details.factories.values()
129
+ except AttributeError:
130
+ return response_factories_list
131
+
132
+ for factory_info in factories:
133
+ factory_name = factory_info.get("facility_name") or _("No facility")
134
+ # Only process Processors
135
+ if factory_info.get("facility_type") != "Processors":
136
+ continue
137
+
138
+ ressource_types = get_resource_type(factory_info["ressources"])
139
+ input_products_list = []
140
+
141
+ # Build Ressource List from Factories
142
+ for input_ressource in ressource_types.values():
143
+ input_ressource = ProductSchema(
144
+ item_id=input_ressource["item_id"],
145
+ item_name=input_ressource["item_name"],
146
+ icon=get_icon_render_url(
147
+ type_id=input_ressource["item_id"],
148
+ type_name=input_ressource["item_name"],
149
+ as_html=True,
150
+ ),
151
+ )
152
+ input_products_list.append(input_ressource)
153
+
154
+ output_product = None
155
+ # Get Output Product if exists
156
+ if factory_info.get("output_product", None) is not None:
157
+ output_product = ProductSchema(
158
+ item_id=factory_info["output_product"]["item_id"],
159
+ item_name=factory_info["output_product"]["item_name"],
160
+ icon=get_icon_render_url(
161
+ type_id=factory_info["output_product"]["item_id"],
162
+ type_name=factory_info["output_product"]["item_name"],
163
+ as_html=True,
164
+ ),
165
+ )
166
+
167
+ is_active = factory_info.get("is_active", False)
168
+
169
+ response_factories_list.append(
170
+ ProduceSchema(
171
+ factory_name=factory_name,
172
+ input_products=input_products_list,
173
+ output_product=output_product,
174
+ is_active=generate_is_active_icon(is_active),
175
+ )
176
+ )
177
+ return response_factories_list
178
+
179
+
180
+ def get_storage_info(planet_details: CharacterPlanetDetails) -> list[StorageSchema]:
181
+ """
182
+ Get the storage information for a planet.
183
+
184
+ Args:
185
+ planet_details (CharacterPlanetDetails): The planetary details object.
186
+ Returns:
187
+ list[StorageSchema]: A list with the storage information for the Planet.
188
+ """
189
+ response_storage_list: list[StorageSchema] = []
190
+
191
+ try:
192
+ factories = planet_details.factories.values()
193
+ except AttributeError:
194
+ return response_storage_list
195
+
196
+ for factory in factories:
197
+ factory_name = factory.get("facility_name") or _("No facility")
198
+
199
+ # Storage Info
200
+ storage = factory.get("storage", {}) or {}
201
+ for type_id, stored in storage.items():
202
+ # Get Eve Type
203
+ type_data = EveType.objects.get_or_create_esi(id=type_id)[0]
204
+
205
+ # Get Amount if Available
206
+ amount = (
207
+ stored.get("amount")
208
+ or stored.get("quantity")
209
+ or stored.get("item_quantity")
210
+ or stored.get("stored")
211
+ or 0
212
+ )
213
+
214
+ # Store Storage Information
215
+ response_storage_list.append(
216
+ StorageSchema(
217
+ factory_name=factory_name,
218
+ product=ProductSchema(
219
+ item_id=type_id,
220
+ item_name=type_data.name,
221
+ item_quantity=int(amount) if amount is not None else None,
222
+ icon=get_icon_render_url(
223
+ type_id=type_id, type_name=type_data.name, as_html=True
224
+ ),
225
+ ),
226
+ )
227
+ )
228
+ return response_storage_list
229
+
230
+
231
+ def generate_is_active_icon(is_active: bool) -> str:
232
+ """
233
+ Generate an HTML icon representing the active status.
234
+
235
+ Args:
236
+ is_active (bool): The active status.
237
+ Returns:
238
+ str: HTML string representing the active status icon.
239
+ """
240
+ color = "danger"
241
+ if is_active:
242
+ color = "success"
243
+ return format_html(
244
+ '<span class="fs-5 text-{}" data-bs-tooltip="aa-ledger" title="{}">⬤</span>',
245
+ color,
246
+ _("Active" if is_active else "Inactive"),
247
+ )
248
+
249
+
250
+ def generate_is_notification_icon(is_notification: bool) -> str:
251
+ """
252
+ Generate an HTML icon representing the notification status.
253
+
254
+ Args:
255
+ is_notification (bool): The notification status.
256
+ Returns:
257
+ str: HTML string representing the notification status icon.
258
+ """
259
+ color = "danger"
260
+ if is_notification:
261
+ color = "success"
262
+ return format_html(
263
+ '<i class="fa-solid fa-bullhorn text-{}" style="margin-left: 5px;" data-bs-tooltip="aa-ledger" title="{}"></i>',
264
+ color,
265
+ _("Active" if is_notification else "Inactive"),
266
+ )
267
+
268
+
269
+ def get_resource_type(ressources):
270
+ """
271
+ Get the resource types.
272
+
273
+ Args:
274
+ ressources (list): List of resource dictionaries.
275
+ Returns:
276
+ dict: Dictionary of unique resource types.
277
+ """
278
+ resource_types = {}
279
+ for ressource in ressources:
280
+ # Create a new entry if item_id not in resource_types
281
+ if ressource["item_id"] not in resource_types:
282
+ resource_types[ressource["item_id"]] = {
283
+ "item_id": ressource["item_id"],
284
+ "item_name": ressource["item_name"],
285
+ }
286
+ return resource_types
287
+
288
+
289
+ def allocate_overall_progress(planet_details: CharacterPlanetDetails) -> float | None:
290
+ """
291
+ Calculate the overall progress percentage of all extractors on the planet.
292
+
293
+ Args:
294
+ planet_details (CharacterPlanetDetails): The planetary details object.
295
+ Returns:
296
+ float | None: The overall progress percentage, or None if no extractors are present.
297
+ """
298
+ progress_sum = 0.0
299
+ valid_extractors = 0
300
+
301
+ try:
302
+ factories = planet_details.factories.values()
303
+ except AttributeError:
304
+ return None
305
+
306
+ for factory in factories:
307
+ extractor = factory.get("extractor", {})
308
+
309
+ # Skip if no extractor present
310
+ if not extractor:
311
+ continue
312
+
313
+ # Get Progress from Extractors
314
+ progress = extractor.get("progress_percentage")
315
+ if progress is not None:
316
+ progress_sum += float(progress)
317
+ valid_extractors += 1
318
+ continue
319
+
320
+ if valid_extractors == 0:
321
+ return None
322
+
323
+ return round(progress_sum / valid_extractors, 2)
324
+
325
+
326
+ def generate_progressbar(percentage: float | None) -> str:
327
+ """
328
+ Generate a progress bar based on the percentage.
329
+
330
+ This function creates an HTML representation of a progress bar
331
+ with a colored percentage display.
332
+
333
+ Args:
334
+ percentage (float): The percentage to display in the progress bar.
335
+ Returns:
336
+ str: HTML string representing the progress bar.
337
+ """
338
+ if percentage is None:
339
+ return str(_("No active extractors"))
340
+
341
+ if percentage > 50:
342
+ progress_value = f'<span class="text-white)">{percentage}%</span>'
343
+ else:
344
+ progress_value = f'<span class="text-dark">{percentage}%</span>'
345
+
346
+ progressbar = f"""
347
+ <div class="progress-outer flex-grow-1 me-2">
348
+ <div class="progress" style="position: relative;">
349
+ <div class="progress-bar progress-bar-warning progress-bar-striped active" role="progressbar" style="width: {percentage}%; box-shadow: -1px 3px 5px rgba(0, 180, 231, 0.9);"></div>
350
+ <div class="fw-bold fs-6 text-center position-absolute top-50 start-50 translate-middle">{progress_value}</div>
351
+ </div>
352
+ </div>
353
+ """
354
+ return progressbar
@@ -0,0 +1,354 @@
1
+ # Third Party
2
+ from ninja import NinjaAPI, schema
3
+
4
+ # Django
5
+ from django.core.handlers.wsgi import WSGIRequest
6
+ from django.db.models import Q
7
+ from django.utils.translation import gettext_lazy as _
8
+
9
+ # Alliance Auth
10
+ from allianceauth.services.hooks import get_extension_logger
11
+
12
+ # AA Ledger
13
+ from ledger import __title__
14
+ from ledger.api.helpers.core import get_alts_queryset, get_characterowner_or_none
15
+ from ledger.api.helpers.icons import (
16
+ get_extractor_info_button,
17
+ get_factory_info_button,
18
+ get_toggle_notification_button,
19
+ )
20
+ from ledger.api.helpers.planetary_helper import (
21
+ FactorySchema,
22
+ ProduceSchema,
23
+ StorageSchema,
24
+ allocate_overall_progress,
25
+ generate_is_active_icon,
26
+ generate_is_notification_icon,
27
+ generate_progressbar,
28
+ get_factories_info,
29
+ get_factory_info,
30
+ get_storage_info,
31
+ )
32
+ from ledger.api.schema import (
33
+ EveTypeSchema,
34
+ ExtractorSchema,
35
+ OwnerSchema,
36
+ PlanetSchema,
37
+ ProgressBarSchema,
38
+ )
39
+ from ledger.helpers.eveonline import (
40
+ get_character_portrait_url,
41
+ get_icon_render_url,
42
+ get_type_render_url,
43
+ )
44
+ from ledger.models.planetary import CharacterPlanetDetails
45
+ from ledger.providers import AppLogger
46
+
47
+ logger = AppLogger(get_extension_logger(__name__), __title__)
48
+
49
+
50
+ class FactoryDetailsResponse(schema.Schema):
51
+ owner: OwnerSchema
52
+ planet: PlanetSchema
53
+ factories: list[ProduceSchema]
54
+ storage: list[StorageSchema]
55
+
56
+
57
+ class ExtractorDetailsResponse(schema.Schema):
58
+ owner: OwnerSchema
59
+ planet: PlanetSchema
60
+ extractors: list[ExtractorSchema]
61
+
62
+
63
+ class PlanetaryDetailsActions(schema.Schema):
64
+ factory_info_button: str
65
+ extractor_info_button: str
66
+ toggle_notification_button: str
67
+
68
+
69
+ class PlanetaryDetails(schema.Schema):
70
+ owner: OwnerSchema
71
+ planet: PlanetSchema
72
+ expired: str | bool
73
+ alarm: str | bool
74
+ progress_bar: str
75
+ factories: list[FactorySchema]
76
+ actions: PlanetaryDetailsActions
77
+
78
+
79
+ class PlanetaryApiEndpoints:
80
+ tags = ["CharacterPlanet"]
81
+
82
+ # pylint: disable=too-many-statements
83
+ def __init__(self, api: NinjaAPI):
84
+ @api.get(
85
+ "character/{character_id}/planet/{planet_id}/details/",
86
+ response={200: list[PlanetaryDetails], 403: str},
87
+ tags=self.tags,
88
+ )
89
+ # pylint: disable=too-many-locals
90
+ def get_planetarydetails(
91
+ request: WSGIRequest, character_id: int, planet_id: int
92
+ ):
93
+ singleview = request.GET.get("single", False)
94
+ perm, character = get_characterowner_or_none(request, character_id)
95
+
96
+ if not perm:
97
+ return 403, str(_("Permission Denied"))
98
+
99
+ if not singleview:
100
+ characters = get_alts_queryset(character)
101
+ else:
102
+ characters = [character]
103
+
104
+ filters = Q(planet__character__in=characters)
105
+ if not planet_id == 0:
106
+ filters &= Q(planet__id=planet_id)
107
+
108
+ planets_details = CharacterPlanetDetails.objects.filter(filters)
109
+ response_planetary_details_list: list[PlanetaryDetails] = []
110
+
111
+ for details in planets_details:
112
+ response_factories_list = get_factories_info(planet_details=details)
113
+
114
+ response_planetary_details = PlanetaryDetails(
115
+ owner=OwnerSchema(
116
+ character_id=details.planet.character.eve_character.character_id,
117
+ character_name=details.planet.character.eve_character.character_name,
118
+ icon=get_character_portrait_url(
119
+ character_id=details.planet.character.eve_character.character_id,
120
+ character_name=details.planet.character.eve_character.character_name,
121
+ as_html=True,
122
+ ),
123
+ ),
124
+ planet=PlanetSchema(
125
+ id=details.planet.eve_planet.id,
126
+ name=details.planet.eve_planet.name,
127
+ type=EveTypeSchema(
128
+ id=details.planet.eve_planet.eve_type.id,
129
+ name=details.planet.eve_planet.eve_type.name,
130
+ icon=get_icon_render_url(
131
+ type_id=details.planet.eve_planet.eve_type.id,
132
+ type_name=details.planet.eve_planet.eve_type.name,
133
+ size=32,
134
+ as_html=True,
135
+ ),
136
+ ),
137
+ upgrade_level=details.planet.upgrade_level,
138
+ num_pins=details.planet.num_pins,
139
+ last_update=details.planet.last_update,
140
+ ),
141
+ factories=response_factories_list,
142
+ expired=generate_is_active_icon(
143
+ is_active=not details.is_expired,
144
+ ),
145
+ alarm=generate_is_notification_icon(
146
+ is_notification=details.notification
147
+ ),
148
+ progress_bar=generate_progressbar(
149
+ allocate_overall_progress(details)
150
+ ),
151
+ actions=PlanetaryDetailsActions(
152
+ factory_info_button=get_factory_info_button(
153
+ planet_details=details
154
+ ),
155
+ extractor_info_button=get_extractor_info_button(
156
+ planet_details=details
157
+ ),
158
+ toggle_notification_button=get_toggle_notification_button(
159
+ planet_details=details
160
+ ),
161
+ ),
162
+ )
163
+ response_planetary_details_list.append(response_planetary_details)
164
+ return response_planetary_details_list
165
+
166
+ @api.get(
167
+ "character/{character_id}/planet/{planet_id}/factory/",
168
+ response={200: FactoryDetailsResponse, 403: dict, 404: dict},
169
+ tags=self.tags,
170
+ )
171
+ def get_factory_details(
172
+ request: WSGIRequest, character_id: int, planet_id: int
173
+ ):
174
+ """
175
+ Get Factory Information for a character's planet.
176
+
177
+ Args:
178
+ request (WSGIRequest): The HTTP request object.
179
+ character_id (int): The ID of the character.
180
+ planet_id (int): The ID of the planet.
181
+ Returns:
182
+ dict: Factory details including owner, planet info, factories, and storage.
183
+ """
184
+ perm, character = get_characterowner_or_none(request, character_id)
185
+
186
+ if not perm:
187
+ return 403, {"error": _("Permission Denied.")}
188
+
189
+ filters = Q(planet__character=character)
190
+ if planet_id != 0:
191
+ filters &= Q(planet__id=planet_id)
192
+
193
+ planet_details = CharacterPlanetDetails.objects.filter(filters).first()
194
+
195
+ if not planet_details:
196
+ return 403, {"error": _("Planet not found.")}
197
+
198
+ response_storage_list = get_storage_info(planet_details=planet_details)
199
+ response_factories_list = get_factory_info(planet_details=planet_details)
200
+
201
+ return FactoryDetailsResponse(
202
+ owner=OwnerSchema(
203
+ character_id=planet_details.planet.character.eve_character.character_id,
204
+ character_name=planet_details.planet.character.eve_character.character_name,
205
+ ),
206
+ planet=PlanetSchema(
207
+ id=planet_details.planet.eve_planet.id,
208
+ name=planet_details.planet.eve_planet.name,
209
+ type=EveTypeSchema(
210
+ id=planet_details.planet.eve_planet.eve_type.id,
211
+ name=planet_details.planet.eve_planet.eve_type.name,
212
+ icon=get_type_render_url(
213
+ type_id=planet_details.planet.eve_planet.eve_type.id,
214
+ size=32,
215
+ as_html=True,
216
+ ),
217
+ ),
218
+ upgrade_level=planet_details.planet.upgrade_level,
219
+ num_pins=planet_details.planet.num_pins,
220
+ last_update=planet_details.planet.last_update,
221
+ ),
222
+ factories=response_factories_list,
223
+ storage=response_storage_list,
224
+ )
225
+
226
+ @api.get(
227
+ "character/{character_id}/planet/{planet_id}/extractor/",
228
+ response={200: ExtractorDetailsResponse, 403: dict, 404: dict},
229
+ tags=self.tags,
230
+ )
231
+ def get_extractor_details(
232
+ request: WSGIRequest, character_id: int, planet_id: int
233
+ ):
234
+ """
235
+ Get Extractor Information for a character's planet.
236
+
237
+ Args:
238
+ request (WSGIRequest): The HTTP request object.
239
+ character_id (int): The ID of the character.
240
+ planet_id (int): The ID of the planet.
241
+ Returns:
242
+ dict: Extractor details including owner, planet info, extractors, and last update.
243
+ """
244
+ perm, character = get_characterowner_or_none(request, character_id)
245
+
246
+ if not perm:
247
+ return 403, {"error": _("Permission Denied.")}
248
+
249
+ filters = Q(planet__character=character)
250
+ if not planet_id == 0:
251
+ filters &= Q(planet__id=planet_id)
252
+
253
+ planet_details = CharacterPlanetDetails.objects.filter(filters).first()
254
+
255
+ if not planet_details:
256
+ return 404, {"error": _("Planet not found.")}
257
+
258
+ response_extractors: list[ExtractorSchema] = []
259
+
260
+ if planet_details.factories:
261
+ for factory in planet_details.factories.values():
262
+ extractor_info = factory.get("extractor", None)
263
+
264
+ # Skip if no extractor info
265
+ if not extractor_info:
266
+ continue
267
+
268
+ extractor = ExtractorSchema(
269
+ item_id=extractor_info["product_type_id"],
270
+ item_name=extractor_info["product_type_name"],
271
+ icon=get_icon_render_url(
272
+ type_id=extractor_info["product_type_id"],
273
+ type_name=extractor_info["product_type_name"],
274
+ as_html=True,
275
+ ),
276
+ install_time=extractor_info["install_time"],
277
+ expiry_time=extractor_info["expiry_time"],
278
+ progress=ProgressBarSchema(
279
+ percentage=str(extractor_info["progress_percentage"]),
280
+ html=generate_progressbar(
281
+ extractor_info["progress_percentage"]
282
+ ),
283
+ ),
284
+ )
285
+ response_extractors.append(extractor)
286
+
287
+ return ExtractorDetailsResponse(
288
+ owner=OwnerSchema(
289
+ character_id=planet_details.planet.character.eve_character.character_id,
290
+ character_name=planet_details.planet.character.eve_character.character_name,
291
+ ),
292
+ planet=PlanetSchema(
293
+ id=planet_details.planet.eve_planet.id,
294
+ name=planet_details.planet.eve_planet.name,
295
+ type=EveTypeSchema(
296
+ id=planet_details.planet.eve_planet.eve_type.id,
297
+ name=planet_details.planet.eve_planet.eve_type.name,
298
+ icon=get_type_render_url(
299
+ type_id=planet_details.planet.eve_planet.eve_type.id,
300
+ size=32,
301
+ as_html=True,
302
+ ),
303
+ ),
304
+ upgrade_level=planet_details.planet.upgrade_level,
305
+ num_pins=planet_details.planet.num_pins,
306
+ last_update=planet_details.planet.last_update,
307
+ ),
308
+ extractors=response_extractors,
309
+ )
310
+
311
+ @api.post(
312
+ "character/{character_id}/planet/{planet_id}/toggle/",
313
+ response={200: dict, 403: dict, 404: dict},
314
+ tags=self.tags,
315
+ )
316
+ def toggle_planet_notification(
317
+ request: WSGIRequest, character_id: int, planet_id: int
318
+ ):
319
+ """
320
+ Toggle Notification for a character's planet.
321
+
322
+ This endpoint toggles the notification setting for a character's planet.
323
+ It validates the user's permission and the existence of the planet before performing the toggle action.
324
+
325
+ Args:
326
+ request (WSGIRequest): The HTTP request object.
327
+ character_id (int): The ID of the character.
328
+ planet_id (int): The ID of the planet.
329
+ Returns:
330
+ dict: A dictionary containing the success status and message.
331
+ """
332
+ perm, character = get_characterowner_or_none(request, character_id)
333
+
334
+ if not perm:
335
+ return 403, {"error": _("Permission Denied.")}
336
+
337
+ filters = Q(planet__character=character)
338
+ if not planet_id == 0:
339
+ filters &= Q(planet__id=planet_id)
340
+
341
+ planets = CharacterPlanetDetails.objects.filter(filters)
342
+
343
+ if not planets.exists():
344
+ return 404, {"error": _("Planet not found.")}
345
+
346
+ on_count = planets.filter(notification=True).count()
347
+ off_count = planets.filter(notification=False).count()
348
+ majority_state = on_count > off_count
349
+
350
+ for planet in planets:
351
+ planet.notification = not majority_state
352
+ planet.save()
353
+ msg = _("Notification toggled successfully.")
354
+ return {"success": True, "message": msg}