aa-structures 4.0.3__tar.gz → 4.0.4__tar.gz

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 (264) hide show
  1. {aa_structures-4.0.3 → aa_structures-4.0.4}/PKG-INFO +7 -6
  2. {aa_structures-4.0.3 → aa_structures-4.0.4}/README.md +4 -4
  3. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/__init__.py +1 -1
  4. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/admin.py +0 -1
  5. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/core/notification_embeds/corporate_embeds.py +17 -21
  6. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/core/notification_embeds/skyhook_embeds.py +4 -8
  7. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/core/notification_embeds/structures_embeds.py +14 -6
  8. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/core/notification_embeds/war_embeds.py +1 -1
  9. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/core/notification_timers.py +1 -2
  10. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/core/serializers.py +4 -4
  11. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/models/notifications.py +2 -4
  12. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/models/owners.py +9 -1
  13. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/models/structures_1.py +2 -0
  14. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/providers.py +0 -2
  15. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tasks.py +13 -8
  16. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/core/notification_embeds/test_main.py +68 -0
  17. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/models/test_notifications_1.py +52 -0
  18. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/models/test_notifications_3.py +35 -0
  19. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/models/test_owners_2.py +44 -0
  20. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/models/test_owners_5.py +22 -0
  21. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/test_admin.py +23 -0
  22. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/test_tasks.py +9 -0
  23. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/views/test_public.py +17 -0
  24. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/views/test_structures.py +60 -1
  25. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/views/public.py +4 -1
  26. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/views/structures.py +23 -11
  27. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/webhooks/core.py +25 -15
  28. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/webhooks/tests/test_core.py +29 -1
  29. aa_structures-4.0.3/.coveragerc +0 -18
  30. aa_structures-4.0.3/.github/workflows/ci-cd.yaml +0 -124
  31. aa_structures-4.0.3/.gitignore +0 -24
  32. aa_structures-4.0.3/.pre-commit-config.yaml +0 -19
  33. aa_structures-4.0.3/.readthedocs.yml +0 -17
  34. aa_structures-4.0.3/.tx/config +0 -10
  35. aa_structures-4.0.3/CHANGELOG.md +0 -1674
  36. aa_structures-4.0.3/CONTRIBUTING.md +0 -14
  37. aa_structures-4.0.3/Makefile +0 -58
  38. aa_structures-4.0.3/aa-structures_models.png +0 -0
  39. aa_structures-4.0.3/codecov.yml +0 -16
  40. aa_structures-4.0.3/docs/Makefile +0 -20
  41. aa_structures-4.0.3/docs/_static/css/rtd_dark.css +0 -114
  42. aa_structures-4.0.3/docs/conf.py +0 -73
  43. aa_structures-4.0.3/docs/index.rst +0 -8
  44. aa_structures-4.0.3/docs/make.bat +0 -35
  45. aa_structures-4.0.3/docs/operations.md +0 -165
  46. aa_structures-4.0.3/docs/requirements.txt +0 -5
  47. aa_structures-4.0.3/docs/user.md +0 -246
  48. aa_structures-4.0.3/runtests.py +0 -20
  49. aa_structures-4.0.3/testauth/__init__.py +0 -1
  50. aa_structures-4.0.3/testauth/celery.py +0 -39
  51. aa_structures-4.0.3/testauth/settings_aa4/__init__.py +0 -0
  52. aa_structures-4.0.3/testauth/settings_aa4/base.py +0 -340
  53. aa_structures-4.0.3/testauth/settings_aa4/local.py +0 -98
  54. aa_structures-4.0.3/testauth/settings_aa5/__init__.py +0 -0
  55. aa_structures-4.0.3/testauth/settings_aa5/base.py +0 -352
  56. aa_structures-4.0.3/testauth/settings_aa5/local.py +0 -98
  57. aa_structures-4.0.3/testauth/urls.py +0 -13
  58. aa_structures-4.0.3/testauth/wsgi.py +0 -14
  59. aa_structures-4.0.3/tools/drop_tables.sql +0 -27
  60. aa_structures-4.0.3/tox.ini +0 -52
  61. {aa_structures-4.0.3 → aa_structures-4.0.4}/LICENSE +0 -0
  62. {aa_structures-4.0.3 → aa_structures-4.0.4}/pyproject.toml +0 -0
  63. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/app_settings.py +0 -0
  64. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/apps.py +0 -0
  65. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/auth_hooks.py +0 -0
  66. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/checks.py +0 -0
  67. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/constants.py +0 -0
  68. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/core/__init__.py +0 -0
  69. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/core/notification_embeds/__init__.py +0 -0
  70. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/core/notification_embeds/billing_embeds.py +0 -0
  71. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/core/notification_embeds/helpers.py +0 -0
  72. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/core/notification_embeds/main.py +0 -0
  73. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/core/notification_embeds/moonmining_embeds.py +0 -0
  74. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/core/notification_embeds/orbital_embeds.py +0 -0
  75. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/core/notification_embeds/sov_embeds.py +0 -0
  76. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/core/notification_embeds/tower_embeds.py +0 -0
  77. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/core/notification_types.py +0 -0
  78. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/core/sovereignty.py +0 -0
  79. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/core/starbases.py +0 -0
  80. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/forms.py +0 -0
  81. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/helpers.py +0 -0
  82. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/locale/de/LC_MESSAGES/django.mo +0 -0
  83. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/locale/de/LC_MESSAGES/django.po +0 -0
  84. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/locale/django.pot +0 -0
  85. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/locale/en/LC_MESSAGES/django.mo +0 -0
  86. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/locale/en/LC_MESSAGES/django.po +0 -0
  87. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/locale/es/LC_MESSAGES/django.mo +0 -0
  88. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/locale/es/LC_MESSAGES/django.po +0 -0
  89. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/locale/fr_FR/LC_MESSAGES/django.mo +0 -0
  90. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/locale/fr_FR/LC_MESSAGES/django.po +0 -0
  91. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/locale/it_IT/LC_MESSAGES/django.mo +0 -0
  92. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/locale/it_IT/LC_MESSAGES/django.po +0 -0
  93. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/locale/ja/LC_MESSAGES/django.mo +0 -0
  94. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/locale/ja/LC_MESSAGES/django.po +0 -0
  95. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/locale/ko_KR/LC_MESSAGES/django.mo +0 -0
  96. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/locale/ko_KR/LC_MESSAGES/django.po +0 -0
  97. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/locale/ru/LC_MESSAGES/django.mo +0 -0
  98. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/locale/ru/LC_MESSAGES/django.po +0 -0
  99. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/locale/uk/LC_MESSAGES/django.mo +0 -0
  100. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/locale/uk/LC_MESSAGES/django.po +0 -0
  101. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
  102. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/locale/zh_Hans/LC_MESSAGES/django.po +0 -0
  103. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/management/commands/__init__.py +0 -0
  104. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/management/commands/structures_load_eve.py +0 -0
  105. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/management/commands/structures_preload_eveuniverse.py +0 -0
  106. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/management/commands/structures_update_poco_planets.py +0 -0
  107. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/managers.py +0 -0
  108. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/migrations/0001_initial_new.py +0 -0
  109. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/migrations/0002_remove_eveuniverse_relation_names.py +0 -0
  110. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/migrations/0003_add_localization_and_unique_key.py +0 -0
  111. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/migrations/0004_improve_localization.py +0 -0
  112. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/migrations/0005_add_notification_types.py +0 -0
  113. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/migrations/0006_add_ownercharacter_disabled.py +0 -0
  114. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/migrations/0007_add_notificationtypes_skyhook_metenox.py +0 -0
  115. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/migrations/0008_add_notificationtypes_skyhook_metenox.py +0 -0
  116. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/migrations/0009_add_project_goal_notifications.py +0 -0
  117. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/migrations/__init__.py +0 -0
  118. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/models/__init__.py +0 -0
  119. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/models/eveuniverse.py +0 -0
  120. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/models/structures_2.py +0 -0
  121. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/openapi_2025-12-16.json +0 -0
  122. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/css/global.css +0 -0
  123. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/css/main.css +0 -0
  124. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/css/public.css +0 -0
  125. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/css/statistics.css +0 -0
  126. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/css/structures.css +0 -0
  127. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/bars-rotate-fade-black-36.svg +0 -0
  128. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/bars-rotate-fade-white-36.svg +0 -0
  129. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/eve_symbol_128.png +0 -0
  130. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/0h.png +0 -0
  131. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/0l.png +0 -0
  132. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/0m.png +0 -0
  133. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/0r.png +0 -0
  134. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/0s.png +0 -0
  135. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/1h.png +0 -0
  136. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/1l.png +0 -0
  137. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/1m.png +0 -0
  138. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/1r.png +0 -0
  139. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/1s.png +0 -0
  140. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/2h.png +0 -0
  141. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/2l.png +0 -0
  142. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/2m.png +0 -0
  143. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/2r.png +0 -0
  144. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/2s.png +0 -0
  145. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/3h.png +0 -0
  146. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/3l.png +0 -0
  147. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/3m.png +0 -0
  148. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/3r.png +0 -0
  149. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/3s.png +0 -0
  150. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/4h.png +0 -0
  151. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/4l.png +0 -0
  152. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/4m.png +0 -0
  153. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/4s.png +0 -0
  154. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/5h.png +0 -0
  155. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/5l.png +0 -0
  156. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/5m.png +0 -0
  157. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/5s.png +0 -0
  158. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/6h.png +0 -0
  159. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/6l.png +0 -0
  160. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/6m.png +0 -0
  161. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/7h.png +0 -0
  162. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/7l.png +0 -0
  163. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/7m.png +0 -0
  164. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/8h.png +0 -0
  165. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/8l.png +0 -0
  166. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/8m.png +0 -0
  167. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/blank.png +0 -0
  168. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/circle.png +0 -0
  169. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/dustwheel.png +0 -0
  170. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/h.png +0 -0
  171. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/l.png +0 -0
  172. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/m.png +0 -0
  173. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/noship.png +0 -0
  174. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/r.png +0 -0
  175. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/tyrannis.png +0 -0
  176. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/tyrannis_blue.png +0 -0
  177. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/tyrannis_darkred.png +0 -0
  178. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/tyrannis_default.png +0 -0
  179. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/panel/tyrannis_revelations.png +0 -0
  180. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/img/structures_logo.png +0 -0
  181. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/js/global.js +0 -0
  182. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/js/public.js +0 -0
  183. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/js/statistics.js +0 -0
  184. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/js/structures.js +0 -0
  185. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/vendor/datatables/plugins/dataTables.rowGroup.min.js +0 -0
  186. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/vendor/datatables/plugins/datetime.js +0 -0
  187. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/vendor/datatables/plugins/filterDropDown.min.js +0 -0
  188. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/vendor/datatables/plugins/rowGroup.bootstrap.min.css +0 -0
  189. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/static/structures/vendor/datatables/plugins/rowGroup.dataTables.min.css +0 -0
  190. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/templates/structures/base.html +0 -0
  191. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/templates/structures/modals/fitting_assets.html +0 -0
  192. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/templates/structures/modals/fitting_gfx.html +0 -0
  193. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/templates/structures/modals/poco_details.html +0 -0
  194. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/templates/structures/modals/starbase_detail.html +0 -0
  195. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/templates/structures/modals/structure_details.html +0 -0
  196. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/templates/structures/modals/tab_general_detail.html +0 -0
  197. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/templates/structures/modals/tab_services_detail.html +0 -0
  198. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/templates/structures/partials/menu.html +0 -0
  199. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/templates/structures/partials/public/poco_list.html +0 -0
  200. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/templates/structures/partials/statistics/structure_summary.html +0 -0
  201. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/templates/structures/partials/structures/active_tags.html +0 -0
  202. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/templates/structures/partials/structures/jump_gate_list.html +0 -0
  203. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/templates/structures/partials/structures/orbital_list.html +0 -0
  204. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/templates/structures/partials/structures/starbase_list.html +0 -0
  205. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/templates/structures/partials/structures/structure_list.html +0 -0
  206. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/templates/structures/public.html +0 -0
  207. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/templates/structures/statistics.html +0 -0
  208. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/templates/structures/structures.html +0 -0
  209. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/templates/structures/templatetags/detail_title.html +0 -0
  210. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/templates/structures/templatetags/list_asset.html +0 -0
  211. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/templates/structures/templatetags/list_item.html +0 -0
  212. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/templates/structures/templatetags/list_tax_item.html +0 -0
  213. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/templates/structures/templatetags/list_title.html +0 -0
  214. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/templatetags/__init__.py +0 -0
  215. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/templatetags/structures.py +0 -0
  216. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/__init__.py +0 -0
  217. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/core/__init__.py +0 -0
  218. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/core/notification_embeds/__init__.py +0 -0
  219. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/core/notification_embeds/test_helpers.py +0 -0
  220. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/core/test_notification_structuretimers.py +0 -0
  221. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/core/test_notification_types.py +0 -0
  222. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/core/test_notifications_timers.py +0 -0
  223. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/core/test_serializers.py +0 -0
  224. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/core/test_sovereignty.py +0 -0
  225. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/core/test_starbases.py +0 -0
  226. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/helpers.py +0 -0
  227. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/integration/__init__.py +0 -0
  228. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/integration/test_tasks.py +0 -0
  229. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/integration/test_views.py +0 -0
  230. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/models/__init__.py +0 -0
  231. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/models/test_eveuniverse.py +0 -0
  232. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/models/test_notifications_2.py +0 -0
  233. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/models/test_notifications_discord.py +0 -0
  234. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/models/test_owners_1.py +0 -0
  235. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/models/test_owners_3.py +0 -0
  236. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/models/test_owners_4.py +0 -0
  237. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/models/test_owners_6.py +0 -0
  238. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/models/test_structures.py +0 -0
  239. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/test_helpers.py +0 -0
  240. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/test_managers_1.py +0 -0
  241. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/test_managers_2.py +0 -0
  242. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/testdata/__init__.py +0 -0
  243. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/testdata/constants.py +0 -0
  244. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/testdata/factories.py +0 -0
  245. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/testdata/generate_notifications.py +0 -0
  246. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/testdata/generate_notifications_2.py +0 -0
  247. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/testdata/generate_structures.py +0 -0
  248. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/testdata/helpers.py +0 -0
  249. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/testdata/notifications.json +0 -0
  250. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/testdata/tasks_loadtest.py +0 -0
  251. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/testdata/test_generate_structures.py +0 -0
  252. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/views/__init__.py +0 -0
  253. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/views/test_service_status.py +0 -0
  254. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/tests/views/test_statistics.py +0 -0
  255. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/urls.py +0 -0
  256. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/views/__init__.py +0 -0
  257. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/views/common.py +0 -0
  258. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/views/statistics.py +0 -0
  259. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/views/status.py +0 -0
  260. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/webhooks/__init__.py +0 -0
  261. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/webhooks/managers.py +0 -0
  262. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/webhooks/models.py +0 -0
  263. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/webhooks/tests/__init__.py +0 -0
  264. {aa_structures-4.0.3 → aa_structures-4.0.4}/structures/webhooks/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: aa-structures
3
- Version: 4.0.3
3
+ Version: 4.0.4
4
4
  Summary: An app for managing Eve Online structures with Alliance Auth.
5
5
  Author-email: Erik Kalkoken <erik.kalkoken@gmail.com>
6
6
  Requires-Python: >=3.10
@@ -35,6 +35,7 @@ Requires-Dist: pook ; extra == "test"
35
35
  Project-URL: Documentation, https://aa-structures.readthedocs.io
36
36
  Project-URL: Homepage, https://github.com/AllianceAuth-Apps/aa-structures
37
37
  Provides-Extra: test
38
+ Import-Name: structures
38
39
 
39
40
  # Structures
40
41
 
@@ -53,7 +54,7 @@ An app for managing Eve Online structures with Alliance Auth.
53
54
 
54
55
  ## Overview
55
56
 
56
- This app is for managing Eve Online structures with [Alliance Auth](https://gitlab.com/allianceauth/allianceauth). It allows all member corporations to see a current list of all their structures in Auth. In addition it allows forwarding Eve Online notifications to Discord.
57
+ Built for [Alliance Auth](https://gitlab.com/allianceauth/allianceauth), it allows all member corporations to see a current list of all their structures in Auth. In addition it allows forwarding Eve Online notifications to Discord.
57
58
 
58
59
  ## Features
59
60
 
@@ -72,7 +73,7 @@ Structures adds the following main features to Alliance Auth:
72
73
  - Automatically adds timers from relevant notifications to [Alliance Auth Structure Timers](https://allianceauth.readthedocs.io/en/latest/features/apps/timerboard.html) or [Structure Timers II](https://github.com/AllianceAuth-Apps/aa-structuretimers) app (if installed)
73
74
  - Fittings, contents of Upwell structures (e.g. fuel blocks and ammo) and current fuel usage
74
75
  - Additional structure notifications generated by the app:
75
- - Configurable alerts to warn about fuel running at in structures and POSes, and which can replace the ESI notifications
76
+ - Configurable alerts to warn about fuel running low in structures and POSes, and which can replace the ESI notifications
76
77
  - Notifications to inform when structures have been refueled (BETA)
77
78
  - Configurable alerts to warn about jump fuel (liquid ozone) running low in jump gates
78
79
  - POS reinforced (BETA)
@@ -80,7 +81,7 @@ Structures adds the following main features to Alliance Auth:
80
81
  - Permissions define which structures are visible to a user based on organization membership
81
82
  - Self-defined tags help to better organize structures
82
83
  - Ability to increase notification response time and sync resilience with multiple sync characters per structure owner
83
- - Automatically sends alerts to users and admin when token become invalid or sync from ESI fails
84
+ - Automatically sends alerts to users and admins when tokens become invalid or sync from ESI fails
84
85
  - Interface for 3rd party monitoring of the services status
85
86
  - Chinese :cn:, English :us:, German :de: and Russian :ru: localization
86
87
 
@@ -103,7 +104,7 @@ The tax rate and access configuration of customs offices is visible too:
103
104
 
104
105
  And you can see the configuration for starbases:
105
106
 
106
- ![Poco details](https://imgpile.com/images/GCxMPr.png)
107
+ ![Starbase details](https://imgpile.com/images/GCxMPr.png)
107
108
 
108
109
  This is an example for a notification posted on Discord:
109
110
 
@@ -15,7 +15,7 @@ An app for managing Eve Online structures with Alliance Auth.
15
15
 
16
16
  ## Overview
17
17
 
18
- This app is for managing Eve Online structures with [Alliance Auth](https://gitlab.com/allianceauth/allianceauth). It allows all member corporations to see a current list of all their structures in Auth. In addition it allows forwarding Eve Online notifications to Discord.
18
+ Built for [Alliance Auth](https://gitlab.com/allianceauth/allianceauth), it allows all member corporations to see a current list of all their structures in Auth. In addition it allows forwarding Eve Online notifications to Discord.
19
19
 
20
20
  ## Features
21
21
 
@@ -34,7 +34,7 @@ Structures adds the following main features to Alliance Auth:
34
34
  - Automatically adds timers from relevant notifications to [Alliance Auth Structure Timers](https://allianceauth.readthedocs.io/en/latest/features/apps/timerboard.html) or [Structure Timers II](https://github.com/AllianceAuth-Apps/aa-structuretimers) app (if installed)
35
35
  - Fittings, contents of Upwell structures (e.g. fuel blocks and ammo) and current fuel usage
36
36
  - Additional structure notifications generated by the app:
37
- - Configurable alerts to warn about fuel running at in structures and POSes, and which can replace the ESI notifications
37
+ - Configurable alerts to warn about fuel running low in structures and POSes, and which can replace the ESI notifications
38
38
  - Notifications to inform when structures have been refueled (BETA)
39
39
  - Configurable alerts to warn about jump fuel (liquid ozone) running low in jump gates
40
40
  - POS reinforced (BETA)
@@ -42,7 +42,7 @@ Structures adds the following main features to Alliance Auth:
42
42
  - Permissions define which structures are visible to a user based on organization membership
43
43
  - Self-defined tags help to better organize structures
44
44
  - Ability to increase notification response time and sync resilience with multiple sync characters per structure owner
45
- - Automatically sends alerts to users and admin when token become invalid or sync from ESI fails
45
+ - Automatically sends alerts to users and admins when tokens become invalid or sync from ESI fails
46
46
  - Interface for 3rd party monitoring of the services status
47
47
  - Chinese :cn:, English :us:, German :de: and Russian :ru: localization
48
48
 
@@ -65,7 +65,7 @@ The tax rate and access configuration of customs offices is visible too:
65
65
 
66
66
  And you can see the configuration for starbases:
67
67
 
68
- ![Poco details](https://imgpile.com/images/GCxMPr.png)
68
+ ![Starbase details](https://imgpile.com/images/GCxMPr.png)
69
69
 
70
70
  This is an example for a notification posted on Discord:
71
71
 
@@ -3,5 +3,5 @@
3
3
  # pylint: disable = invalid-name
4
4
  default_app_config = "structures.apps.StructuresConfig"
5
5
 
6
- __version__ = "4.0.3"
6
+ __version__ = "4.0.4"
7
7
  __title__ = "Structures"
@@ -326,7 +326,6 @@ class NotificationBaseAdmin(admin.ModelAdmin):
326
326
  message = format_lazy("{first} {second}", first=first, second=second)
327
327
  else:
328
328
  message = first
329
- message = format_lazy(first)
330
329
  self.message_user(request, message)
331
330
 
332
331
 
@@ -38,12 +38,11 @@ class NotificationCorpAppNewMsg(NotificationCorpCharEmbed):
38
38
  self._description = _(
39
39
  "New application from %(character_name)s to join %(corporation_name)s:\n"
40
40
  "> %(application_text)s"
41
- % {
42
- "character_name": self._character_link,
43
- "corporation_name": self._corporation_link,
44
- "application_text": self._application_text,
45
- }
46
- )
41
+ ) % {
42
+ "character_name": self._character_link,
43
+ "corporation_name": self._corporation_link,
44
+ "application_text": self._application_text,
45
+ }
47
46
  self._color = Webhook.Color.INFO
48
47
 
49
48
 
@@ -178,11 +177,10 @@ class NotificationCorpGoalClosed(NotificationCorpGoalEmbed):
178
177
  self._description = _(
179
178
  "Project **%(goal_name)s** has ben closed by %(closer)s "
180
179
  "and will not accept further contributions."
181
- % {
182
- "goal_name": self._goal_name,
183
- "closer": gen_eve_entity_link(closer),
184
- }
185
- )
180
+ ) % {
181
+ "goal_name": self._goal_name,
182
+ "closer": gen_eve_entity_link(closer),
183
+ }
186
184
  self._color = Webhook.Color.INFO
187
185
 
188
186
 
@@ -193,11 +191,10 @@ class NotificationCorpGoalCompleted(NotificationCorpGoalEmbed):
193
191
  self._description = _(
194
192
  "Project **%(goal_name)s** created by %(creator)s "
195
193
  "has been successfully completed after reaching it's target."
196
- % {
197
- "goal_name": self._goal_name,
198
- "creator": self._creator_link,
199
- }
200
- )
194
+ ) % {
195
+ "goal_name": self._goal_name,
196
+ "creator": self._creator_link,
197
+ }
201
198
  self._color = Webhook.Color.SUCCESS
202
199
 
203
200
 
@@ -208,9 +205,8 @@ class NotificationCorpGoalCreated(NotificationCorpGoalEmbed):
208
205
  self._description = _(
209
206
  "Project **%(goal_name)s** has been created by %(creator)s "
210
207
  "and is open for contributions."
211
- % {
212
- "goal_name": self._goal_name,
213
- "creator": self._creator_link,
214
- }
215
- )
208
+ ) % {
209
+ "goal_name": self._goal_name,
210
+ "creator": self._creator_link,
211
+ }
216
212
  self._color = Webhook.Color.INFO
@@ -77,12 +77,8 @@ class NotificationSkyhookUnderAttack(NotificationSkyhookEmbed):
77
77
  def __init__(self, n: Notification) -> None:
78
78
  super().__init__(n)
79
79
  self._title = _("Skyhook under attack")
80
- self._description += _(
81
- "is under attack by %(attacker)s.\n"
82
- "%(damage_text)s"
83
- % {
84
- "attacker": self.gen_attacker_link(),
85
- "damage_text": self.compile_damage_text("Percentage"),
86
- }
87
- )
80
+ self._description += _("is under attack by %(attacker)s.\n%(damage_text)s") % {
81
+ "attacker": self.gen_attacker_link(),
82
+ "damage_text": self.compile_damage_text("Percentage"),
83
+ }
88
84
  self._color = Webhook.Color.DANGER
@@ -95,7 +95,9 @@ class NotificationStructureJumpFuelAlert(NotificationStructureEmbed):
95
95
  super().__init__(notification)
96
96
  self._title = _("Jump gate low on Liquid Ozone")
97
97
  threshold_str = f"{self._data['threshold']:,}"
98
- quantity_str = f"{self._structure.jump_fuel_quantity():,}"
98
+ quantity_str = (
99
+ f"{self._structure.jump_fuel_quantity():,}" if self._structure else "?"
100
+ )
99
101
  self._description += _(
100
102
  "is below %(threshold)s units on Liquid Ozone.\n"
101
103
  "Remaining units: %(remaining)s."
@@ -262,13 +264,19 @@ class NotificationStructureReinforceChange(NotificationBaseEmbed):
262
264
 
263
265
  def __init__(self, notification: Notification) -> None:
264
266
  super().__init__(notification)
267
+ structure_ids = [
268
+ structure_info[0] for structure_info in self._data["allStructureInfo"]
269
+ ]
270
+ structures_by_id = {
271
+ structure.id: structure
272
+ for structure in Structure.objects.select_related_defaults().filter(
273
+ id__in=structure_ids
274
+ )
275
+ }
265
276
  all_structure_info = []
266
277
  for structure_info in self._data["allStructureInfo"]:
267
- try:
268
- structure = Structure.objects.select_related_defaults().get(
269
- id=structure_info[0]
270
- )
271
- except Structure.DoesNotExist:
278
+ structure = structures_by_id.get(structure_info[0])
279
+ if structure is None:
272
280
  all_structure_info.append(
273
281
  self.StructureInfo(
274
282
  name=structure_info[1],
@@ -296,7 +296,7 @@ class NotificationCorpWarSurrenderMsg(NotificationWarBaseEmbed):
296
296
  class NotificationWarHQRemovedFromSpace(NotificationWarBaseEmbed):
297
297
  def __init__(self, notification: Notification) -> None:
298
298
  super().__init__(notification)
299
- war_hq = self._data["warHQ"]
299
+ war_hq = strip_tags(self._data["warHQ"])
300
300
  time_declared = ldap_time_2_datetime(self._data["timeDeclared"])
301
301
  self._title = _("WarHQ %s lost") % war_hq
302
302
  self._description = _(
@@ -323,8 +323,6 @@ def _gen_timer_moon_extraction(notif: Notification) -> bool:
323
323
  timer_processed = True
324
324
 
325
325
  case NotificationType.MOONMINING_EXTRACTION_CANCELLED:
326
- timer_processed = True
327
-
328
326
  for notification in Notification.objects.filter(
329
327
  notif_type=NotificationType.MOONMINING_EXTRACTION_STARTED,
330
328
  owner=notif.owner,
@@ -335,6 +333,7 @@ def _gen_timer_moon_extraction(notif: Notification) -> bool:
335
333
  parsed_text_2 = notification.parsed_text()
336
334
  my_structure_type_id = parsed_text_2["structureTypeID"]
337
335
  if my_structure_type_id == parsed_text["structureTypeID"]:
336
+ timer_processed = True
338
337
  eve_time_2 = _extract_eve_time(parsed_text_2)
339
338
 
340
339
  if AuthTimer:
@@ -140,10 +140,10 @@ class _AbstractStructureListSerializer(ABC):
140
140
  self, structure: Structure, row: dict, check_tags: bool = True
141
141
  ):
142
142
  structure_name_html = escape(structure.name)
143
- tags = []
144
- if check_tags and structure.tags.exists():
145
- tags += [x.html for x in structure.tags.all()]
146
- structure_name_html += format_html("<br>{}", mark_safe(" ".join(tags)))
143
+ if check_tags:
144
+ tags = [x.html for x in structure.tags.all()]
145
+ if tags:
146
+ structure_name_html += format_html("<br>{}", mark_safe(" ".join(tags)))
147
147
 
148
148
  row["structure_name_and_tags"] = structure_name_html
149
149
 
@@ -309,10 +309,7 @@ class NotificationBase(models.Model):
309
309
  else:
310
310
  structures_qs = self.calc_related_structures()
311
311
 
312
- if (
313
- structures_qs.exists()
314
- and structures_qs.filter(webhooks__isnull=False).count() == 1
315
- ):
312
+ if structures_qs.count() == 1 and structures_qs.first().webhooks.exists():
316
313
  webhooks_qs = structures_qs.first().webhooks
317
314
  else:
318
315
  webhooks_qs = self.owner.webhooks
@@ -896,6 +893,7 @@ class JumpFuelAlertConfig(BaseFuelAlertConfig):
896
893
  )
897
894
  .filter(is_active=True)
898
895
  .filter(Q(owners__isnull=False) | Q(structures__isnull=False))
896
+ .distinct()
899
897
  )
900
898
 
901
899
 
@@ -633,6 +633,11 @@ class Owner(models.Model):
633
633
  self, token: Token, structures: List[dict]
634
634
  ) -> bool:
635
635
  count = 0
636
+ existing_names = dict(
637
+ self.structures.filter(
638
+ id__in=[s["structure_id"] for s in structures]
639
+ ).values_list("id", "name")
640
+ )
636
641
  for s in structures:
637
642
  try:
638
643
  structure_info_obj = (
@@ -654,7 +659,8 @@ class Owner(models.Model):
654
659
  self._report_esi_issue(
655
660
  f"fetch structure #{s['structure_id']}", ex, token
656
661
  )
657
- s["name"] = "(no data)"
662
+ # Don't clobber a previously resolved name on a transient failure.
663
+ s["name"] = existing_names.get(s["structure_id"]) or "(no data)"
658
664
  else:
659
665
  structure_info = structure_info_obj.model_dump()
660
666
  count += 1
@@ -1215,6 +1221,7 @@ class Owner(models.Model):
1215
1221
  )
1216
1222
  for notif in new_eve_notifications:
1217
1223
  notif.send_to_configured_webhooks()
1224
+ notifications_count += 1
1218
1225
 
1219
1226
  new_generated_notifications: models.QuerySet[GeneratedNotification] = (
1220
1227
  self.generatednotification_set.filter(**my_filter)
@@ -1223,6 +1230,7 @@ class Owner(models.Model):
1223
1230
  )
1224
1231
  for notif in new_generated_notifications:
1225
1232
  notif.send_to_configured_webhooks()
1233
+ notifications_count += 1
1226
1234
 
1227
1235
  if (
1228
1236
  not new_eve_notifications.exists()
@@ -259,6 +259,8 @@ class Structure(models.Model): # pylint: disable = too-many-public-methods
259
259
  verbose_name=_("fuel expires at"),
260
260
  help_text=_("Date on which the structure will run out of fuel"),
261
261
  )
262
+ # despite the name, also true when the structure only has fuel/cargo/fighters, not just a fit
263
+ # TODO: rename to has_items with the next migration
262
264
  has_fitting = models.BooleanField(
263
265
  null=True,
264
266
  default=None,
@@ -20,10 +20,8 @@ esi = ESIClientProvider(
20
20
  "GetCorporationsCorporationIdStructures",
21
21
  "GetSovereigntyMap",
22
22
  "GetUniverseStructuresStructureId",
23
- "GetUniverseStructuresStructureId",
24
23
  "PostCorporationsCorporationIdAssetsNames",
25
24
  "PostCorporationsCorporationIdAssetsLocations",
26
- "PostCorporationsCorporationIdAssetsNames",
27
25
  ],
28
26
  spec_file=spec_file,
29
27
  )
@@ -49,14 +49,15 @@ def update_sov_map(_self):
49
49
  @shared_task(time_limit=STRUCTURES_TASKS_TIME_LIMIT)
50
50
  def update_structures():
51
51
  """Update all structures for all active owners from ESI."""
52
- for owner in Owner.objects.all():
53
- if owner.is_active:
54
- update_structures_for_owner.delay(owner.pk)
52
+ has_active_owner = False
53
+ has_alliance_main = False
54
+ for owner in Owner.objects.filter(is_active=True):
55
+ has_active_owner = True
56
+ update_structures_for_owner.delay(owner.pk)
57
+ if owner.is_alliance_main:
58
+ has_alliance_main = True
55
59
 
56
- if (
57
- Owner.objects.filter(is_active=True).count() > 0
58
- and Owner.objects.filter(is_active=True, is_alliance_main=True).count() == 0
59
- ):
60
+ if has_active_owner and not has_alliance_main:
60
61
  logger.warning(
61
62
  "No owner configured to process alliance wide notifications. "
62
63
  "Please set 'is alliance main' to True for the designated owner."
@@ -204,7 +205,11 @@ def generate_new_timers_for_owner(owner_pk: int):
204
205
  owner.add_or_remove_timers_from_notifications()
205
206
 
206
207
 
207
- @shared_task(time_limit=STRUCTURES_TASKS_TIME_LIMIT)
208
+ @shared_task(
209
+ base=QueueOnce,
210
+ once={"keys": ["owner_pk"]},
211
+ time_limit=STRUCTURES_TASKS_TIME_LIMIT,
212
+ )
208
213
  def send_new_notifications_for_owner(owner_pk: int):
209
214
  """Send new notifications to Discord."""
210
215
  owner = Owner.objects.get(pk=owner_pk)
@@ -1,9 +1,12 @@
1
1
  import datetime as dt
2
+ import re
2
3
 
3
4
  import dhooks_lite
4
5
 
5
6
  from django.core.cache import cache
7
+ from django.db import connection
6
8
  from django.test import TestCase, override_settings
9
+ from django.test.utils import CaptureQueriesContext
7
10
  from django.utils.timezone import now
8
11
  from eveuniverse.tests.testdata.factories_2 import EveMoonFactory, EveSolarSystemFactory
9
12
 
@@ -240,6 +243,23 @@ class TestNotificationEmbedsGenerate(NoSocketsTestCase):
240
243
  self.assertEqual(discord_embed.footer.text, "Structures")
241
244
  self.assertIn("structures_logo.png", discord_embed.footer.icon_url)
242
245
 
246
+ def test_should_generate_jump_fuel_alert_embed_when_structure_no_longer_exists(
247
+ self,
248
+ ):
249
+ # given
250
+ structure = StructureFactory(owner=self.owner)
251
+ notification = Notification.create_from_structure(
252
+ structure,
253
+ notif_type=NotificationType.STRUCTURE_JUMP_FUEL_ALERT,
254
+ threshold=1000,
255
+ )
256
+ structure.delete()
257
+ notification_embed = NotificationBaseEmbed.create(notification)
258
+ # when
259
+ discord_embed = notification_embed.generate_embed()
260
+ # then
261
+ self.assertIsInstance(discord_embed, dhooks_lite.Embed)
262
+
243
263
  def test_should_not_break_with_too_large_description(self):
244
264
  # given
245
265
  notification = Notification.objects.get(notification_id=1000000403)
@@ -404,4 +424,52 @@ class TestEveNotificationEmbeds(NoSocketsTestCase):
404
424
  self.assertTrue(obj.description)
405
425
  self.assertTrue(obj.description)
406
426
  self.assertTrue(obj.description)
427
+
428
+
429
+ class TestNotificationStructureReinforceChange(NoSocketsTestCase):
430
+ @classmethod
431
+ def setUpClass(cls):
432
+ super().setUpClass()
433
+ cls.owner = OwnerFactory()
434
+
435
+ def test_should_batch_fetch_structures_and_handle_missing_ones(self):
436
+ # given
437
+ structure_1 = StructureFactory(owner=self.owner)
438
+ structure_2 = StructureFactory(owner=self.owner)
439
+ missing_structure_id = 1_599_999_999_999
440
+ notif = NotificationFactory(
441
+ owner=self.owner,
442
+ notif_type=NotificationType.STRUCTURE_REINFORCEMENT_CHANGED,
443
+ text_from_dict={
444
+ "allStructureInfo": [
445
+ [structure_1.id, structure_1.name, structure_1.eve_type_id],
446
+ [
447
+ missing_structure_id,
448
+ "Ghost Structure",
449
+ structure_2.eve_type_id,
450
+ ],
451
+ [structure_2.id, structure_2.name, structure_2.eve_type_id],
452
+ ],
453
+ "hour": 19,
454
+ "numStructures": 3,
455
+ "timestamp": 132141703753688216,
456
+ "weekday": 255,
457
+ },
458
+ )
459
+ # when
460
+ with CaptureQueriesContext(connection) as ctx:
461
+ embed = NotificationBaseEmbed.create(notif)
462
+ # then: structures are fetched with a single batched query, not one per entry
463
+ structure_queries = [
464
+ query
465
+ for query in ctx.captured_queries
466
+ if re.search(r"\bstructures_structure\b", query["sql"])
467
+ ]
468
+ self.assertEqual(len(structure_queries), 1)
469
+
470
+ obj = embed.generate_embed()
471
+ self.assertIn(structure_1.name, obj.description)
472
+ self.assertIn(structure_2.name, obj.description)
473
+ self.assertIn("Ghost Structure", obj.description)
474
+ self.assertIn("(unknown)", obj.description)
407
475
  self.assertTrue(obj.description)
@@ -369,6 +369,58 @@ class TestNotificationRelevantWebhooks(NoSocketsTestCase):
369
369
  result_qs, Webhook.objects.filter(pk=webhook_structure.pk)
370
370
  )
371
371
 
372
+ def test_should_return_structure_webhooks_when_structure_has_multiple_webhooks(
373
+ self,
374
+ ):
375
+ # given
376
+ webhook_owner = WebhookFactory(
377
+ notification_types=[NotificationType.STRUCTURE_UNDER_ATTACK]
378
+ )
379
+ owner = OwnerFactory(webhooks=[webhook_owner])
380
+ structure = StructureFactory(owner=owner)
381
+ webhook_structure_1 = WebhookFactory(
382
+ notification_types=[NotificationType.STRUCTURE_UNDER_ATTACK]
383
+ )
384
+ webhook_structure_2 = WebhookFactory(
385
+ notification_types=[NotificationType.STRUCTURE_UNDER_ATTACK]
386
+ )
387
+ structure.webhooks.add(webhook_structure_1, webhook_structure_2)
388
+ notif = NotificationFactory(
389
+ owner=owner,
390
+ notif_type=NotificationType.STRUCTURE_UNDER_ATTACK,
391
+ text_from_dict={
392
+ "allianceID": 3011,
393
+ "allianceLinkData": ["showinfo", 16159, 3011],
394
+ "allianceName": "Big Bad Alliance",
395
+ "armorPercentage": 98.65129050962584,
396
+ "charID": 1011,
397
+ "corpLinkData": ["showinfo", 2, 2011],
398
+ "corpName": "Bad Company",
399
+ "hullPercentage": 100.0,
400
+ "shieldPercentage": 4.704536686417284e-14,
401
+ "solarsystemID": structure.eve_solar_system_id,
402
+ "structureID": structure.id,
403
+ "structureShowInfoData": [
404
+ "showinfo",
405
+ structure.eve_type_id,
406
+ structure.id,
407
+ ],
408
+ "structureTypeID": structure.eve_type_id,
409
+ },
410
+ )
411
+ # when
412
+ result_qs = notif.relevant_webhooks()
413
+ # then
414
+ # A structure with 2+ direct webhooks must still use its own webhooks,
415
+ # not silently fall back to the owner's defaults.
416
+ self.assertQuerySetEqual(
417
+ result_qs,
418
+ Webhook.objects.filter(
419
+ pk__in=[webhook_structure_1.pk, webhook_structure_2.pk]
420
+ ),
421
+ ordered=False,
422
+ )
423
+
372
424
  def test_should_return_owner_webhooks_when_notif_has_multiple_structures(self):
373
425
  # given
374
426
  webhook_owner = WebhookFactory(
@@ -15,8 +15,10 @@ from structures.tests.testdata.factories import (
15
15
  StarbaseFactory,
16
16
  StructureFactory,
17
17
  )
18
+ from structures.tests.testdata.helpers import load_notification_entities
18
19
 
19
20
  MODULE_PATH = "structures.models.notifications"
21
+ TIMERS_MODULE_PATH = "structures.core.notification_timers"
20
22
 
21
23
 
22
24
  class TestGeneratedNotification(NoSocketsTestCase):
@@ -194,3 +196,36 @@ class TestProcessTimers(NoSocketsTestCase):
194
196
  result = notif.add_or_remove_timer()
195
197
  # then
196
198
  self.assertFalse(result)
199
+
200
+
201
+ @mock.patch(MODULE_PATH + ".STRUCTURES_ADD_TIMERS", True)
202
+ @mock.patch(TIMERS_MODULE_PATH + ".STRUCTURES_MOON_EXTRACTION_TIMERS_ENABLED", True)
203
+ class TestMoonExtractionCancelledTimer(NoSocketsTestCase):
204
+ @classmethod
205
+ def setUpClass(cls):
206
+ super().setUpClass()
207
+ cls.owner = OwnerFactory()
208
+ load_notification_entities(cls.owner)
209
+
210
+ def test_should_not_mark_processed_when_no_matching_started_notification(self):
211
+ # given
212
+ notif = NotificationFactory(
213
+ owner=self.owner,
214
+ notif_type=NotificationType.MOONMINING_EXTRACTION_CANCELLED,
215
+ text_from_dict={
216
+ "cancelledBy": 1001,
217
+ "cancelledByLink": '<a href="showinfo:1383//1001">Bruce Wayne</a>',
218
+ "moonID": 40161465,
219
+ "solarSystemID": 30002537,
220
+ "structureID": 1000000000002,
221
+ "structureLink": ('<a href="showinfo:35835//1000000000002">Dummy</a>'),
222
+ "structureName": "Dummy",
223
+ "structureTypeID": 35835,
224
+ },
225
+ )
226
+ # when
227
+ result = notif.add_or_remove_timer()
228
+ # then
229
+ self.assertFalse(result)
230
+ notif.refresh_from_db()
231
+ self.assertFalse(notif.is_timer_added)
@@ -343,6 +343,50 @@ class TestUpdateStructuresEsi(TestCaseWithClearCache):
343
343
  structure = Structure.objects.get(id=structure_id)
344
344
  self.assertEqual(structure.name, "(no data)")
345
345
 
346
+ @pook.on
347
+ def test_should_keep_last_known_name_when_universe_endpoint_fails(self):
348
+ # given
349
+ owner = OwnerFactory(user=self.user, structures_last_update_at=None)
350
+ structure_id = 1000000000001
351
+ solar_system = EveSolarSystemFactory()
352
+ structure_type = CitadelTypeFactory()
353
+ StructureFactory(
354
+ id=structure_id,
355
+ owner=owner,
356
+ eve_type=structure_type,
357
+ eve_solar_system=solar_system,
358
+ name="My Awesome Structure",
359
+ )
360
+ pook.get(
361
+ f"https://esi.evetech.net/corporations/{self.corporation_id}/structures",
362
+ reply=200,
363
+ response_json=[
364
+ {
365
+ "corporation_id": self.corporation_id,
366
+ "profile_id": 101853,
367
+ "reinforce_hour": 18,
368
+ "state": "shield_vulnerable",
369
+ "structure_id": structure_id,
370
+ "system_id": solar_system.id,
371
+ "type_id": structure_type.id,
372
+ },
373
+ ],
374
+ )
375
+ pook.get(
376
+ f"https://esi.evetech.net/universe/structures/{structure_id}",
377
+ reply=500,
378
+ response_json={"error": "internal_error"},
379
+ )
380
+
381
+ # when
382
+ owner.update_structures_esi()
383
+
384
+ # then
385
+ owner.refresh_from_db()
386
+ self.assertFalse(owner.is_structure_sync_fresh)
387
+ structure = Structure.objects.get(id=structure_id)
388
+ self.assertEqual(structure.name, "My Awesome Structure")
389
+
346
390
  @pook.on
347
391
  def test_tags_are_not_modified_by_update(self):
348
392
  # given