aa-ledger 1.0.3__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.3.dist-info → aa_ledger-2.0.0.dist-info}/METADATA +6 -6
  2. aa_ledger-2.0.0.dist-info/RECORD +267 -0
  3. {aa_ledger-1.0.3.dist-info → aa_ledger-2.0.0.dist-info}/WHEEL +1 -1
  4. ledger/__init__.py +2 -2
  5. ledger/admin.py +23 -18
  6. ledger/api/__init__.py +23 -7
  7. ledger/api/{ledger/admin.py → admin.py} +25 -31
  8. ledger/api/alliance.py +755 -0
  9. ledger/api/character.py +786 -0
  10. ledger/api/corporation.py +1141 -0
  11. ledger/api/{helpers.py → helpers/core.py} +33 -33
  12. ledger/api/helpers/icons.py +372 -0
  13. ledger/api/helpers/planetary_helper.py +354 -0
  14. ledger/api/planetary.py +354 -0
  15. ledger/api/schema.py +240 -15
  16. ledger/app_settings.py +18 -26
  17. ledger/auth_hooks.py +2 -2
  18. ledger/constants.py +50 -177
  19. ledger/decorators.py +2 -46
  20. ledger/forms.py +133 -39
  21. ledger/helpers/billboard.py +194 -144
  22. ledger/helpers/cache.py +105 -0
  23. ledger/helpers/discord.py +2 -4
  24. ledger/helpers/eveonline.py +160 -0
  25. ledger/helpers/ledger_data.py +23 -0
  26. ledger/helpers/ref_type.py +53 -78
  27. ledger/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  28. ledger/locale/cs_CZ/LC_MESSAGES/django.po +349 -193
  29. ledger/locale/de/LC_MESSAGES/django.mo +0 -0
  30. ledger/locale/de/LC_MESSAGES/django.po +528 -379
  31. ledger/locale/django.pot +717 -553
  32. ledger/locale/es/LC_MESSAGES/django.mo +0 -0
  33. ledger/locale/es/LC_MESSAGES/django.po +349 -194
  34. ledger/locale/fr_FR/LC_MESSAGES/django.mo +0 -0
  35. ledger/locale/fr_FR/LC_MESSAGES/django.po +349 -193
  36. ledger/locale/it_IT/LC_MESSAGES/django.mo +0 -0
  37. ledger/locale/it_IT/LC_MESSAGES/django.po +349 -193
  38. ledger/locale/ja/LC_MESSAGES/django.mo +0 -0
  39. ledger/locale/ja/LC_MESSAGES/django.po +348 -193
  40. ledger/locale/ko_KR/LC_MESSAGES/django.mo +0 -0
  41. ledger/locale/ko_KR/LC_MESSAGES/django.po +349 -193
  42. ledger/locale/nl_NL/LC_MESSAGES/django.mo +0 -0
  43. ledger/locale/nl_NL/LC_MESSAGES/django.po +349 -193
  44. ledger/locale/pl_PL/LC_MESSAGES/django.mo +0 -0
  45. ledger/locale/pl_PL/LC_MESSAGES/django.po +350 -193
  46. ledger/locale/ru/LC_MESSAGES/django.mo +0 -0
  47. ledger/locale/ru/LC_MESSAGES/django.po +348 -193
  48. ledger/locale/sk/LC_MESSAGES/django.mo +0 -0
  49. ledger/locale/sk/LC_MESSAGES/django.po +348 -193
  50. ledger/locale/uk/LC_MESSAGES/django.mo +0 -0
  51. ledger/locale/uk/LC_MESSAGES/django.po +348 -193
  52. ledger/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
  53. ledger/locale/zh_Hans/LC_MESSAGES/django.po +348 -193
  54. ledger/managers/character_audit_manager.py +28 -20
  55. ledger/managers/character_journal_manager.py +187 -358
  56. ledger/managers/character_mining_manager.py +64 -30
  57. ledger/managers/character_planetary_manager.py +185 -138
  58. ledger/managers/corporation_audit_manager.py +36 -27
  59. ledger/managers/corporation_journal_manager.py +94 -57
  60. ledger/managers/general_manager.py +12 -8
  61. ledger/migrations/0018_remove_characterplanet_ledger_char_planet__58a5b6_idx_and_more.py +44 -0
  62. ledger/migrations/0019_rename_characteraudit_characterowner_and_more.py +48 -0
  63. ledger/models/__init__.py +5 -11
  64. ledger/models/characteraudit.py +101 -109
  65. ledger/models/corporationaudit.py +94 -49
  66. ledger/models/general.py +105 -211
  67. ledger/models/helpers/update_manager.py +302 -0
  68. ledger/models/planetary.py +60 -205
  69. ledger/providers.py +101 -0
  70. ledger/static/ledger/css/{ledger.css → aa-ledger.css} +54 -28
  71. ledger/static/ledger/js/aa-ledger.js +124 -0
  72. ledger/static/ledger/js/charts.js +25 -1
  73. ledger/static/ledger/js/view-alliance-ledger.js +383 -0
  74. ledger/static/ledger/js/view-character-ledger.js +388 -0
  75. ledger/static/ledger/js/view-corporation-ledger.js +402 -0
  76. ledger/static/ledger/js/view-planetary.js +492 -0
  77. ledger/static/ledger/libs/amCharts/5.14.4/js/flow.js +2 -0
  78. ledger/static/ledger/libs/amCharts/5.14.4/js/index.js +2 -0
  79. ledger/static/ledger/libs/amCharts/5.14.4/js/percent.js +2 -0
  80. ledger/static/ledger/libs/amCharts/5.14.4/js/themes/Animated.js +2 -0
  81. ledger/static/ledger/libs/amCharts/5.14.4/js/themes/Dark.js +2 -0
  82. ledger/static/ledger/libs/amCharts/5.14.4/js/xy.js +2 -0
  83. ledger/static/ledger/libs/datatables/2.3.5/css/dataTables.bootstrap5.css +610 -0
  84. ledger/static/ledger/libs/datatables/2.3.5/js/dataTables.bootstrap5.js +122 -0
  85. ledger/static/ledger/libs/datatables/2.3.5/js/dataTables.js +14127 -0
  86. ledger/static/ledger/libs/datatables/Extensions/ColumnControl/1.1.1/css/columnControl.bootstrap5.css +516 -0
  87. ledger/static/ledger/libs/datatables/Extensions/ColumnControl/1.1.1/css/columnControl.dataTables.css +529 -0
  88. ledger/static/ledger/libs/datatables/Extensions/ColumnControl/1.1.1/js/columnControl.bootstrap5.js +73 -0
  89. ledger/static/ledger/libs/datatables/Extensions/ColumnControl/1.1.1/js/dataTables.columnControl.js +3090 -0
  90. ledger/static/ledger/libs/datatables/Extensions/FixedHeader/4.0.4/css/fixedHeader.bootstrap5.css +20 -0
  91. ledger/static/ledger/libs/datatables/Extensions/FixedHeader/4.0.4/js/dataTables.fixedHeader.js +1203 -0
  92. ledger/static/ledger/libs/datatables/Extensions/FixedHeader/4.0.4/js/fixedHeader.bootstrap5.js +59 -0
  93. ledger/tasks.py +157 -146
  94. ledger/templates/ledger/base.html +59 -21
  95. ledger/templates/ledger/bundles/aa-ledger-css.html +3 -0
  96. ledger/templates/ledger/bundles/aa-ledger-js.html +3 -0
  97. ledger/templates/ledger/bundles/view-alliance-ledger-js.html +14 -0
  98. ledger/templates/ledger/bundles/view-character-ledger-js.html +15 -0
  99. ledger/templates/ledger/bundles/view-character-planetary-css.html +3 -0
  100. ledger/templates/ledger/bundles/view-character-planetary-js.html +4 -0
  101. ledger/templates/ledger/bundles/view-corporation-ledger-js.html +15 -0
  102. ledger/templates/ledger/partials/modal/confirm.html +0 -1
  103. ledger/templates/ledger/partials/modal/request-accept-delete-alliance.html +38 -0
  104. ledger/templates/ledger/partials/modal/request-accept-delete-character.html +38 -0
  105. ledger/templates/ledger/partials/modal/request-accept-delete-corporation.html +38 -0
  106. ledger/templates/ledger/partials/modal/request-accept-switch-notification.html +38 -0
  107. ledger/templates/ledger/partials/modal/request-view-alliance-details.html +26 -0
  108. ledger/templates/ledger/partials/modal/request-view-character-details.html +26 -0
  109. ledger/templates/ledger/partials/modal/request-view-corporation-details.html +26 -0
  110. ledger/templates/ledger/partials/modal/request-view-extractor.html +32 -0
  111. ledger/templates/ledger/partials/modal/request-view-factory.html +31 -0
  112. ledger/templates/ledger/partials/{menu → navigation}/administration.html +8 -0
  113. ledger/templates/ledger/partials/{menu → navigation}/navigation.html +2 -2
  114. ledger/templates/ledger/partials/{administration → view-alliance-administration}/alliance_corporations.html +3 -3
  115. ledger/templates/ledger/partials/view-alliance-administration/dashboard.html +81 -0
  116. ledger/templates/ledger/partials/view-alliance-ledger/alliance-billboard.html +25 -0
  117. ledger/templates/ledger/partials/view-alliance-ledger/alliance-ledger-details.html +21 -0
  118. ledger/templates/ledger/partials/view-alliance-ledger/alliance-table.html +24 -0
  119. ledger/templates/ledger/partials/view-alliance-ledger/information/daily.html +18 -0
  120. ledger/templates/ledger/partials/view-alliance-ledger/information/hourly.html +18 -0
  121. ledger/templates/ledger/partials/view-alliance-ledger/information/summary.html +19 -0
  122. ledger/templates/ledger/partials/{administration → view-character-administration}/character.html +1 -9
  123. ledger/templates/ledger/partials/{administration → view-character-administration}/dashboard.html +0 -34
  124. ledger/templates/ledger/partials/view-character-ledger/character-billboard.html +25 -0
  125. ledger/templates/ledger/partials/view-character-ledger/character-ledger-details.html +21 -0
  126. ledger/templates/ledger/partials/view-character-ledger/character-table.html +25 -0
  127. ledger/templates/ledger/partials/view-character-ledger/information/daily.html +18 -0
  128. ledger/templates/ledger/partials/view-character-ledger/information/hourly.html +18 -0
  129. ledger/templates/ledger/partials/view-character-ledger/information/summary.html +19 -0
  130. ledger/templates/ledger/partials/view-character-planetary/extractor-table.html +24 -0
  131. ledger/templates/ledger/partials/view-character-planetary/factory-table.html +24 -0
  132. ledger/templates/ledger/partials/view-character-planetary/planetary-table.html +22 -0
  133. ledger/templates/ledger/partials/view-character-planetary/storage-table.html +23 -0
  134. ledger/templates/ledger/partials/{administration → view-corporation-administration}/corporation.html +5 -13
  135. ledger/templates/ledger/partials/{administration → view-corporation-administration}/corporation_characters.html +1 -1
  136. ledger/templates/ledger/partials/view-corporation-administration/dashboard.html +81 -0
  137. ledger/templates/ledger/partials/view-corporation-ledger/corporation-billboard.html +25 -0
  138. ledger/templates/ledger/partials/view-corporation-ledger/corporation-ledger-details.html +21 -0
  139. ledger/templates/ledger/partials/view-corporation-ledger/corporation-table.html +26 -0
  140. ledger/templates/ledger/partials/view-corporation-ledger/information/daily.html +18 -0
  141. ledger/templates/ledger/partials/view-corporation-ledger/information/hourly.html +18 -0
  142. ledger/templates/ledger/partials/view-corporation-ledger/information/summary.html +19 -0
  143. ledger/templates/ledger/view-administration.html +62 -0
  144. ledger/templates/ledger/view-alliance-administration.html +49 -0
  145. ledger/templates/ledger/view-alliance-ledger.html +72 -0
  146. ledger/templates/ledger/view-alliance-overview.html +131 -0
  147. ledger/templates/ledger/view-character-administration.html +42 -0
  148. ledger/templates/ledger/view-character-ledger.html +73 -0
  149. ledger/templates/ledger/view-character-overview.html +135 -0
  150. ledger/templates/ledger/view-character-planetary-overview.html +135 -0
  151. ledger/templates/ledger/view-character-planetary.html +73 -0
  152. ledger/templates/ledger/view-corporation-administration.html +42 -0
  153. ledger/templates/ledger/view-corporation-ledger.html +73 -0
  154. ledger/templates/ledger/view-corporation-overview.html +131 -0
  155. ledger/templatetags/ledger.py +3 -5
  156. ledger/tests/__init__.py +187 -0
  157. ledger/tests/test_admin.py +164 -68
  158. ledger/tests/test_auth_hook.py +31 -13
  159. ledger/tests/test_decarators.py +14 -79
  160. ledger/tests/test_discord_installed.py +0 -1
  161. ledger/tests/test_helpers/test_ledger_data.py +19 -0
  162. ledger/tests/test_managers/test_character_audit_manager.py +111 -69
  163. ledger/tests/test_managers/test_character_journal_manager.py +48 -208
  164. ledger/tests/test_managers/test_character_mining_manager.py +37 -16
  165. ledger/tests/test_managers/test_corporation_division_manager.py +66 -28
  166. ledger/tests/test_managers/test_corporation_journal_manager.py +39 -42
  167. ledger/tests/test_managers/test_general_manager.py +78 -18
  168. ledger/tests/test_managers/test_planetary_manager.py +73 -32
  169. ledger/tests/test_models/test_characteraudit.py +58 -74
  170. ledger/tests/test_models/test_characterminingledger.py +20 -26
  171. ledger/tests/test_models/test_characterwalletjournal.py +10 -33
  172. ledger/tests/test_models/test_corporationaudit.py +41 -35
  173. ledger/tests/test_models/test_corporationwalletjournal.py +35 -32
  174. ledger/tests/test_models/test_general.py +44 -11
  175. ledger/tests/test_models/test_planetary.py +14 -80
  176. ledger/tests/test_templatetags.py +2 -7
  177. ledger/tests/test_views/corporation/test_add_corp.py +16 -35
  178. ledger/tests/test_views/corporation/test_delete_corporation.py +66 -42
  179. ledger/tests/test_views/test_access.py +512 -545
  180. ledger/tests/test_views/test_add_ally.py +57 -46
  181. ledger/tests/test_views/test_add_char.py +21 -33
  182. ledger/tests/test_views/test_delete_character.py +24 -21
  183. ledger/tests/testdata/README_ESI_STUB.md +430 -0
  184. ledger/tests/testdata/esi_stub_openapi.py +511 -0
  185. ledger/tests/testdata/integrations/__init__.py +0 -0
  186. ledger/tests/testdata/{load_eveuniverse.py → integrations/eveuniverse.py} +0 -1
  187. ledger/tests/testdata/integrations/planetary.py +13 -0
  188. ledger/tests/testdata/json/factory.json +281 -0
  189. ledger/tests/testdata/json/inactive.json +281 -0
  190. ledger/tests/testdata/json/pins.json +175 -272
  191. ledger/tests/testdata/json/route.json +95 -528
  192. ledger/tests/testdata/test_esi_stub.py +468 -0
  193. ledger/tests/testdata/utils.py +601 -0
  194. ledger/thirdparty/charlink_hook.py +60 -30
  195. ledger/urls.py +0 -135
  196. ledger/views/alliance/add_ally.py +2 -4
  197. ledger/views/alliance/alliance_ledger.py +64 -147
  198. ledger/views/character/add_char.py +8 -10
  199. ledger/views/character/character_ledger.py +60 -126
  200. ledger/views/character/planetary.py +5 -98
  201. ledger/views/corporation/add_corp.py +10 -12
  202. ledger/views/corporation/corporation_ledger.py +65 -327
  203. ledger/views/index.py +92 -30
  204. aa_ledger-1.0.3.dist-info/RECORD +0 -236
  205. ledger/api/api_helper/planetary_helper.py +0 -107
  206. ledger/api/ledger/__init__.py +0 -7
  207. ledger/api/ledger/planetary.py +0 -231
  208. ledger/helpers/alliance.py +0 -317
  209. ledger/helpers/character.py +0 -251
  210. ledger/helpers/core.py +0 -665
  211. ledger/helpers/corporation.py +0 -427
  212. ledger/helpers/data_exporter.py +0 -452
  213. ledger/static/ledger/js/planetary-confirm.js +0 -66
  214. ledger/static/ledger/js/planetary.js +0 -143
  215. ledger/templates/ledger/admin.html +0 -43
  216. ledger/templates/ledger/allyledger/admin/alliance_administration.html +0 -46
  217. ledger/templates/ledger/allyledger/admin/alliance_overview.html +0 -108
  218. ledger/templates/ledger/allyledger/alliance_ledger.html +0 -86
  219. ledger/templates/ledger/bundles/character-ledger-bundles.html +0 -66
  220. ledger/templates/ledger/bundles/corporation-ledger-bundles.html +0 -75
  221. ledger/templates/ledger/bundles/ledger-bundles.html +0 -23
  222. ledger/templates/ledger/bundles/ledger-css.html +0 -3
  223. ledger/templates/ledger/bundles/planetary-bundles.html +0 -50
  224. ledger/templates/ledger/bundles/table-css.html +0 -3
  225. ledger/templates/ledger/charledger/admin/character_administration.html +0 -39
  226. ledger/templates/ledger/charledger/admin/character_overview.html +0 -106
  227. ledger/templates/ledger/charledger/character_ledger.html +0 -94
  228. ledger/templates/ledger/charledger/planetary/admin/planetary_overview.html +0 -123
  229. ledger/templates/ledger/charledger/planetary/planetary_ledger.html +0 -54
  230. ledger/templates/ledger/corpledger/admin/corporation_administration.html +0 -39
  231. ledger/templates/ledger/corpledger/admin/corporation_overview.html +0 -108
  232. ledger/templates/ledger/corpledger/corporation_ledger.html +0 -129
  233. ledger/templates/ledger/data-export.html +0 -78
  234. ledger/templates/ledger/error.html +0 -31
  235. ledger/templates/ledger/partials/form/error-message.html +0 -1
  236. ledger/templates/ledger/partials/information/daily.html +0 -56
  237. ledger/templates/ledger/partials/information/day.html +0 -48
  238. ledger/templates/ledger/partials/information/error.html +0 -8
  239. ledger/templates/ledger/partials/information/hourly.html +0 -53
  240. ledger/templates/ledger/partials/information/summary.html +0 -88
  241. ledger/templates/ledger/partials/information/view_character_content.html +0 -35
  242. ledger/templates/ledger/partials/modal/switchalarm_confirm.html +0 -39
  243. ledger/templates/ledger/partials/modal/view_extractor.html +0 -48
  244. ledger/templates/ledger/partials/modal/view_factory.html +0 -123
  245. ledger/templates/ledger/partials/table/char-ledger.html +0 -85
  246. ledger/templates/ledger/partials/table/corp-ledger.html +0 -66
  247. ledger/templates/ledger/partials/table/planetary.html +0 -18
  248. ledger/templates/ledger/partials/thirdparty/billboard.html +0 -22
  249. ledger/templates/ledger/partials/view/card.html +0 -160
  250. ledger/templates/ledger/permission.html +0 -2
  251. ledger/tests/test_helpers/test_billboard.py +0 -11
  252. ledger/tests/test_helpers/test_data_exporter.py +0 -207
  253. ledger/tests/test_tasks.py +0 -282
  254. ledger/tests/test_view_helpers/test_core.py +0 -47
  255. ledger/tests/test_views/corporation/test_corporation.py +0 -267
  256. ledger/tests/test_views/test_planetary.py +0 -137
  257. ledger/tests/testdata/esi_stub.py +0 -109
  258. ledger/tests/testdata/esi_stub_migration.py +0 -80
  259. ledger/tests/testdata/generate_characteraudit.py +0 -106
  260. ledger/tests/testdata/generate_corporationaudit.py +0 -74
  261. ledger/tests/testdata/generate_events.py +0 -31
  262. ledger/tests/testdata/generate_miningledger.py +0 -13
  263. ledger/tests/testdata/generate_planets.py +0 -48
  264. ledger/tests/testdata/generate_walletjournal.py +0 -42
  265. ledger/tests/testdata/json/czarno-pins.json +0 -240
  266. ledger/tests/testdata/json/czarno-routes.json +0 -165
  267. ledger/tests/testdata/json/pins2.json +0 -538
  268. {aa_ledger-1.0.3.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}