aa-structures 2.10.0__tar.gz → 2.12.0__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 (226) hide show
  1. {aa_structures-2.10.0 → aa_structures-2.12.0}/PKG-INFO +1 -1
  2. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/__init__.py +1 -1
  3. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/admin.py +43 -4
  4. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/app_settings.py +1 -1
  5. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/core/notification_embeds/main.py +18 -0
  6. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/managers.py +17 -4
  7. aa_structures-2.12.0/structures/migrations/0006_add_ownercharacter_disabled.py +27 -0
  8. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/models/notifications.py +5 -1
  9. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/models/owners.py +104 -51
  10. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/core/notification_embeds/test_main.py +20 -0
  11. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/models/test_notifications_discord.py +21 -0
  12. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/models/test_owners_1.py +95 -52
  13. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/models/test_owners_4.py +21 -5
  14. aa_structures-2.12.0/structures/tests/models/test_owners_6.py +31 -0
  15. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/test_admin.py +65 -0
  16. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/test_managers_2.py +3 -2
  17. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/testdata/factories.py +3 -4
  18. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/views/test_structures.py +58 -5
  19. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/views/structures.py +2 -2
  20. {aa_structures-2.10.0 → aa_structures-2.12.0}/LICENSE +0 -0
  21. {aa_structures-2.10.0 → aa_structures-2.12.0}/README.md +0 -0
  22. {aa_structures-2.10.0 → aa_structures-2.12.0}/pyproject.toml +0 -0
  23. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/apps.py +0 -0
  24. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/auth_hooks.py +0 -0
  25. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/constants.py +0 -0
  26. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/core/__init__.py +0 -0
  27. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/core/notification_embeds/__init__.py +0 -0
  28. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/core/notification_embeds/billing_embeds.py +0 -0
  29. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/core/notification_embeds/corporate_embeds.py +0 -0
  30. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/core/notification_embeds/helpers.py +0 -0
  31. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/core/notification_embeds/moonmining_embeds.py +0 -0
  32. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/core/notification_embeds/orbital_embeds.py +0 -0
  33. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/core/notification_embeds/sov_embeds.py +0 -0
  34. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/core/notification_embeds/structures_embeds.py +0 -0
  35. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/core/notification_embeds/tower_embeds.py +0 -0
  36. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/core/notification_embeds/war_embeds.py +0 -0
  37. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/core/notification_timers.py +0 -0
  38. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/core/notification_types.py +0 -0
  39. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/core/serializers.py +0 -0
  40. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/core/sovereignty.py +0 -0
  41. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/core/starbases.py +0 -0
  42. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/forms.py +0 -0
  43. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/helpers.py +0 -0
  44. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/locale/de/LC_MESSAGES/django.mo +0 -0
  45. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/locale/de/LC_MESSAGES/django.po +0 -0
  46. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/locale/django.pot +0 -0
  47. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/locale/en/LC_MESSAGES/django.mo +0 -0
  48. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/locale/en/LC_MESSAGES/django.po +0 -0
  49. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/locale/es/LC_MESSAGES/django.mo +0 -0
  50. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/locale/es/LC_MESSAGES/django.po +0 -0
  51. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/locale/fr_FR/LC_MESSAGES/django.mo +0 -0
  52. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/locale/fr_FR/LC_MESSAGES/django.po +0 -0
  53. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/locale/it_IT/LC_MESSAGES/django.mo +0 -0
  54. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/locale/it_IT/LC_MESSAGES/django.po +0 -0
  55. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/locale/ja/LC_MESSAGES/django.mo +0 -0
  56. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/locale/ja/LC_MESSAGES/django.po +0 -0
  57. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/locale/ko_KR/LC_MESSAGES/django.mo +0 -0
  58. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/locale/ko_KR/LC_MESSAGES/django.po +0 -0
  59. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/locale/ru/LC_MESSAGES/django.mo +0 -0
  60. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/locale/ru/LC_MESSAGES/django.po +0 -0
  61. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/locale/uk/LC_MESSAGES/django.mo +0 -0
  62. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/locale/uk/LC_MESSAGES/django.po +0 -0
  63. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
  64. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/locale/zh_Hans/LC_MESSAGES/django.po +0 -0
  65. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/management/commands/__init__.py +0 -0
  66. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/management/commands/structures_load_eve.py +0 -0
  67. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/management/commands/structures_preload_eveuniverse.py +0 -0
  68. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/management/commands/structures_update_poco_planets.py +0 -0
  69. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/migrations/0001_initial_new.py +0 -0
  70. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/migrations/0002_remove_eveuniverse_relation_names.py +0 -0
  71. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/migrations/0003_add_localization_and_unique_key.py +0 -0
  72. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/migrations/0004_improve_localization.py +0 -0
  73. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/migrations/0005_add_notification_types.py +0 -0
  74. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/migrations/__init__.py +0 -0
  75. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/models/__init__.py +0 -0
  76. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/models/eveuniverse.py +0 -0
  77. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/models/structures_1.py +0 -0
  78. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/models/structures_2.py +0 -0
  79. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/providers.py +0 -0
  80. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/css/global.css +0 -0
  81. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/css/main.css +0 -0
  82. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/css/public.css +0 -0
  83. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/css/statistics.css +0 -0
  84. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/css/structures.css +0 -0
  85. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/bars-rotate-fade-black-36.svg +0 -0
  86. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/bars-rotate-fade-white-36.svg +0 -0
  87. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/eve_symbol_128.png +0 -0
  88. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/0h.png +0 -0
  89. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/0l.png +0 -0
  90. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/0m.png +0 -0
  91. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/0r.png +0 -0
  92. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/0s.png +0 -0
  93. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/1h.png +0 -0
  94. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/1l.png +0 -0
  95. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/1m.png +0 -0
  96. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/1r.png +0 -0
  97. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/2h.png +0 -0
  98. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/2l.png +0 -0
  99. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/2m.png +0 -0
  100. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/2r.png +0 -0
  101. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/3h.png +0 -0
  102. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/3l.png +0 -0
  103. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/3m.png +0 -0
  104. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/3r.png +0 -0
  105. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/4h.png +0 -0
  106. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/4l.png +0 -0
  107. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/4m.png +0 -0
  108. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/4s.png +0 -0
  109. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/5h.png +0 -0
  110. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/5l.png +0 -0
  111. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/5m.png +0 -0
  112. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/5s.png +0 -0
  113. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/6h.png +0 -0
  114. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/6l.png +0 -0
  115. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/6m.png +0 -0
  116. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/7h.png +0 -0
  117. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/7l.png +0 -0
  118. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/7m.png +0 -0
  119. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/8h.png +0 -0
  120. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/8l.png +0 -0
  121. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/8m.png +0 -0
  122. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/blank.png +0 -0
  123. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/circle.png +0 -0
  124. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/dustwheel.png +0 -0
  125. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/h.png +0 -0
  126. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/l.png +0 -0
  127. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/m.png +0 -0
  128. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/noship.png +0 -0
  129. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/r.png +0 -0
  130. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/tyrannis.png +0 -0
  131. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/tyrannis_blue.png +0 -0
  132. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/tyrannis_darkred.png +0 -0
  133. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/tyrannis_default.png +0 -0
  134. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/panel/tyrannis_revelations.png +0 -0
  135. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/img/structures_logo.png +0 -0
  136. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/js/global.js +0 -0
  137. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/js/public.js +0 -0
  138. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/js/statistics.js +0 -0
  139. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/js/structures.js +0 -0
  140. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/vendor/datatables/plugins/dataTables.rowGroup.min.js +0 -0
  141. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/vendor/datatables/plugins/datetime.js +0 -0
  142. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/vendor/datatables/plugins/filterDropDown.min.js +0 -0
  143. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/vendor/datatables/plugins/rowGroup.bootstrap.min.css +0 -0
  144. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/static/structures/vendor/datatables/plugins/rowGroup.dataTables.min.css +0 -0
  145. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tasks.py +0 -0
  146. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/templates/structures/base.html +0 -0
  147. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/templates/structures/modals/fitting_assets.html +0 -0
  148. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/templates/structures/modals/fitting_gfx.html +0 -0
  149. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/templates/structures/modals/poco_details.html +0 -0
  150. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/templates/structures/modals/starbase_detail.html +0 -0
  151. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/templates/structures/modals/structure_details.html +0 -0
  152. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/templates/structures/modals/tab_general_detail.html +0 -0
  153. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/templates/structures/modals/tab_services_detail.html +0 -0
  154. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/templates/structures/partials/menu.html +0 -0
  155. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/templates/structures/partials/public/poco_list.html +0 -0
  156. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/templates/structures/partials/statistics/structure_summary.html +0 -0
  157. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/templates/structures/partials/structures/active_tags.html +0 -0
  158. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/templates/structures/partials/structures/jump_gates_list.html +0 -0
  159. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/templates/structures/partials/structures/poco_list.html +0 -0
  160. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/templates/structures/partials/structures/starbase_list.html +0 -0
  161. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/templates/structures/partials/structures/structure_list.html +0 -0
  162. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/templates/structures/public.html +0 -0
  163. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/templates/structures/statistics.html +0 -0
  164. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/templates/structures/structures.html +0 -0
  165. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/templates/structures/templatetags/detail_title.html +0 -0
  166. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/templates/structures/templatetags/list_asset.html +0 -0
  167. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/templates/structures/templatetags/list_item.html +0 -0
  168. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/templates/structures/templatetags/list_tax_item.html +0 -0
  169. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/templates/structures/templatetags/list_title.html +0 -0
  170. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/templatetags/__init__.py +0 -0
  171. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/templatetags/structures.py +0 -0
  172. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/__init__.py +0 -0
  173. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/core/__init__.py +0 -0
  174. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/core/notification_embeds/__init__.py +0 -0
  175. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/core/notification_embeds/test_helpers.py +0 -0
  176. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/core/test_notification_structuretimers.py +0 -0
  177. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/core/test_notification_types.py +0 -0
  178. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/core/test_notifications_timerboard.py +0 -0
  179. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/core/test_serializers.py +0 -0
  180. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/core/test_sovereignty.py +0 -0
  181. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/core/test_starbases.py +0 -0
  182. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/integration/__init__.py +0 -0
  183. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/integration/test_tasks.py +0 -0
  184. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/integration/test_views.py +0 -0
  185. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/models/__init__.py +0 -0
  186. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/models/test_eveuniverse.py +0 -0
  187. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/models/test_notifications_1.py +0 -0
  188. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/models/test_notifications_2.py +0 -0
  189. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/models/test_notifications_3.py +0 -0
  190. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/models/test_owners_2.py +0 -0
  191. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/models/test_owners_3.py +0 -0
  192. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/models/test_owners_5.py +0 -0
  193. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/models/test_structures.py +0 -0
  194. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/test_helpers.py +0 -0
  195. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/test_managers_1.py +0 -0
  196. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/test_tasks.py +0 -0
  197. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/testdata/__init__.py +0 -0
  198. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/testdata/create_eveuniverse.py +0 -0
  199. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/testdata/entities.json +0 -0
  200. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/testdata/esi_data.json +0 -0
  201. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/testdata/eveuniverse.json +0 -0
  202. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/testdata/generate_notifications.py +0 -0
  203. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/testdata/generate_notifications_2.py +0 -0
  204. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/testdata/generate_structures.py +0 -0
  205. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/testdata/helpers.py +0 -0
  206. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/testdata/load_eveuniverse.py +0 -0
  207. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/testdata/tasks_loadtest.py +0 -0
  208. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/testdata/test_generate_structures.py +0 -0
  209. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/views/__init__.py +0 -0
  210. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/views/test_public.py +0 -0
  211. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/views/test_service_status.py +0 -0
  212. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/views/test_statistics.py +0 -0
  213. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/tests/views/utils.py +0 -0
  214. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/urls.py +0 -0
  215. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/views/__init__.py +0 -0
  216. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/views/common.py +0 -0
  217. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/views/public.py +0 -0
  218. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/views/statistics.py +0 -0
  219. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/views/status.py +0 -0
  220. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/webhooks/__init__.py +0 -0
  221. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/webhooks/core.py +0 -0
  222. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/webhooks/managers.py +0 -0
  223. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/webhooks/models.py +0 -0
  224. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/webhooks/tests/__init__.py +0 -0
  225. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/webhooks/tests/test_core.py +0 -0
  226. {aa_structures-2.10.0 → aa_structures-2.12.0}/structures/webhooks/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: aa-structures
3
- Version: 2.10.0
3
+ Version: 2.12.0
4
4
  Summary: App for managing Eve Online structures with Alliance Auth.
5
5
  Author-email: Erik Kalkoken <kalkoken87@gmail.com>
6
6
  Requires-Python: >=3.8
@@ -3,5 +3,5 @@
3
3
  # pylint: disable = invalid-name
4
4
  default_app_config = "structures.apps.StructuresConfig"
5
5
 
6
- __version__ = "2.10.0"
6
+ __version__ = "2.12.0"
7
7
  __title__ = "Structures"
@@ -348,6 +348,29 @@ class OwnerCharacterAdminInline(admin.TabularInline):
348
348
  return False
349
349
 
350
350
 
351
+ class DisabledCharactersFilter(admin.SimpleListFilter):
352
+ title = _("has disabled characters")
353
+ parameter_name = "has_disabled_characters"
354
+
355
+ def lookups(self, request, model_admin):
356
+ return (
357
+ ("yes", _("yes")),
358
+ ("no", _("no")),
359
+ )
360
+
361
+ def queryset(self, request, queryset):
362
+ """Return the filtered queryset"""
363
+ if self.value() == "yes":
364
+ return queryset.annotate_characters_count().filter(
365
+ characters_disabled_count__gt=0
366
+ )
367
+ if self.value() == "no":
368
+ return queryset.annotate_characters_count().filter(
369
+ characters_disabled_count=0
370
+ )
371
+ return queryset
372
+
373
+
351
374
  @admin.register(Owner)
352
375
  class OwnerAdmin(admin.ModelAdmin):
353
376
  list_display = (
@@ -364,6 +387,7 @@ class OwnerAdmin(admin.ModelAdmin):
364
387
  list_filter = (
365
388
  "is_active",
366
389
  "is_up",
390
+ DisabledCharactersFilter,
367
391
  ("corporation__alliance", admin.RelatedOnlyFieldListFilter),
368
392
  "has_default_pings_enabled",
369
393
  "is_alliance_main",
@@ -376,6 +400,7 @@ class OwnerAdmin(admin.ModelAdmin):
376
400
  "fetch_notifications",
377
401
  "deactivate_owners",
378
402
  "activate_owners",
403
+ "reset_characters",
379
404
  )
380
405
  inlines = (OwnerCharacterAdminInline,)
381
406
  filter_horizontal = ("ping_groups", "webhooks")
@@ -485,9 +510,13 @@ class OwnerAdmin(admin.ModelAdmin):
485
510
  def has_add_permission(self, request):
486
511
  return False
487
512
 
488
- @admin.display(ordering="characters_count_2", description=_("characters"))
489
- def _characters(self, obj: Owner) -> int:
490
- return obj.characters_count_2
513
+ @admin.display(ordering="characters_enabled_count", description=_("characters"))
514
+ def _characters(self, obj: Owner) -> str:
515
+ enabled = obj.characters_enabled_count
516
+ disabled = obj.characters_disabled_count
517
+ if not disabled:
518
+ return enabled
519
+ return f"{enabled} ({disabled})"
491
520
 
492
521
  @admin.display(description=_("default pings"), boolean=True)
493
522
  def _has_default_pings_enabled(self, obj: Owner):
@@ -549,6 +578,16 @@ class OwnerAdmin(admin.ModelAdmin):
549
578
  queryset.update(is_active=False)
550
579
  self.message_user(request, _("Deactivated %d owners") % queryset.count())
551
580
 
581
+ @admin.action(description=_("Reset disabled characters for selected owners"))
582
+ def reset_characters(self, request, queryset):
583
+ owner_pks = queryset.values_list("pk", flat=True)
584
+ OwnerCharacter.objects.filter(
585
+ owner__pk__in=list(owner_pks), is_enabled=False
586
+ ).update(is_enabled=True, disabled_reason="", error_count=0)
587
+ self.message_user(
588
+ request, _("Characters have been reset for %d owners") % len(owner_pks)
589
+ )
590
+
552
591
  @admin.action(description=_("Update all from EVE server for selected owners"))
553
592
  def update_all(self, request, queryset):
554
593
  for obj in queryset:
@@ -631,7 +670,7 @@ class OwnerAdmin(admin.ModelAdmin):
631
670
  def _assets_last_update_fresh(self, obj: Owner) -> bool:
632
671
  return obj.is_assets_sync_fresh
633
672
 
634
- @admin.action(description=_("structures Count"))
673
+ @admin.display(description=_("structures Count"))
635
674
  def _structures_count(self, obj: Owner) -> int:
636
675
  return obj.structures.count()
637
676
 
@@ -55,7 +55,7 @@ STRUCTURES_FEATURE_STARBASES = clean_setting("STRUCTURES_FEATURE_STARBASES", Tru
55
55
  STRUCTURES_ESI_DIRECTOR_ERROR_MAX_RETRIES = clean_setting(
56
56
  "STRUCTURES_ESI_DIRECTOR_ERROR_MAX_RETRIES", 3
57
57
  )
58
- """Max retries before a character is deleted when ESI claims the character
58
+ """Max retries before a character is disabled when ESI claims the character
59
59
  is not a director (Since this sometimes is reported wrongly by ESI).
60
60
  """
61
61
 
@@ -10,6 +10,8 @@ import dhooks_lite
10
10
  from django.conf import settings
11
11
  from django.utils.translation import gettext as _
12
12
 
13
+ from allianceauth.services.hooks import get_extension_logger
14
+ from app_utils.logging import LoggerAddTag
13
15
  from app_utils.urls import reverse_absolute, static_file_absolute_url
14
16
 
15
17
  from structures import __title__
@@ -19,6 +21,8 @@ from structures.models.notifications import Notification, NotificationBase, Webh
19
21
 
20
22
  from .helpers import target_datetime_formatted
21
23
 
24
+ logger = LoggerAddTag(get_extension_logger(__name__), __title__)
25
+
22
26
 
23
27
  class NotificationBaseEmbed:
24
28
  """Base class for all notification embeds.
@@ -141,6 +145,20 @@ class NotificationBaseEmbed:
141
145
  footer_text += f" #{my_text}"
142
146
  footer_icon_url = footer_icon_url if is_absolute_url(footer_icon_url) else None
143
147
  footer = dhooks_lite.Footer(text=footer_text, icon_url=footer_icon_url)
148
+ max_description = dhooks_lite.Embed.MAX_DESCRIPTION
149
+ if self._description and len(self._description) > max_description:
150
+ logger.warning(
151
+ "%s: Description of notification is too long: %s",
152
+ self,
153
+ self._description,
154
+ )
155
+ self._description = self._description[:max_description]
156
+ max_title = dhooks_lite.Embed.MAX_TITLE
157
+ if self._title and len(self._title) > max_title:
158
+ logger.warning(
159
+ "%s: Title of notification is too long: %s", self, self._title
160
+ )
161
+ self._title = self._title[:max_title]
144
162
  return dhooks_lite.Embed(
145
163
  author=author,
146
164
  color=self._color,
@@ -181,14 +181,27 @@ GeneratedNotificationManager = GeneratedNotificationManagerBase.from_queryset(
181
181
 
182
182
  class OwnerQuerySet(models.QuerySet):
183
183
  def annotate_characters_count(self) -> models.QuerySet:
184
- """Add character count annotation."""
185
- return self.annotate(
186
- characters_count_2=Count(
184
+ """Add character count annotations."""
185
+ qs = self.annotate(
186
+ characters_enabled_count=Count(
187
+ "characters",
188
+ filter=Q(
189
+ characters__character_ownership__isnull=False,
190
+ characters__is_enabled=True,
191
+ ),
192
+ distinct=True,
193
+ )
194
+ ).annotate(
195
+ characters_disabled_count=Count(
187
196
  "characters",
188
- filter=Q(characters__character_ownership__isnull=False),
197
+ filter=Q(
198
+ characters__character_ownership__isnull=False,
199
+ characters__is_enabled=False,
200
+ ),
189
201
  distinct=True,
190
202
  )
191
203
  )
204
+ return qs
192
205
 
193
206
  def structures_last_updated(self) -> Optional[dt.datetime]:
194
207
  """Date/time when structures were last updated for any of the active owners."""
@@ -0,0 +1,27 @@
1
+ # Generated by Django 4.0.10 on 2024-06-23 14:26
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("structures", "0005_add_notification_types"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name="ownercharacter",
15
+ name="disabled_reason",
16
+ field=models.TextField(default=""),
17
+ ),
18
+ migrations.AddField(
19
+ model_name="ownercharacter",
20
+ name="is_enabled",
21
+ field=models.BooleanField(
22
+ default=True,
23
+ help_text="Disabled characters are not used for syncing owners",
24
+ verbose_name="is enabled",
25
+ ),
26
+ ),
27
+ ]
@@ -410,11 +410,15 @@ class NotificationBase(models.Model):
410
410
  try:
411
411
  embed, ping_type = self._generate_embed(webhook.language_code)
412
412
  except OSError as ex:
413
- logger.warning("%s: Failed to generate embed: %s", self, ex, exc_info=True)
413
+ logger.error("%s: Failed to generate embed: %s", self, ex, exc_info=True)
414
414
  return False
415
415
 
416
416
  content = self._create_content_with_pings(webhook, ping_type)
417
417
  content += self._add_discord_group_pings(webhook)
418
+ max_content = dhooks_lite.Webhook.MAX_CHARACTERS
419
+ if content and len(content) > max_content:
420
+ logger.error("%s: Content of notification is too long: %s", self, content)
421
+ return False
418
422
 
419
423
  username, avatar_url = self._gen_avatar()
420
424
  new_queue_size = webhook.send_message(
@@ -338,12 +338,15 @@ class Owner(models.Model):
338
338
  f"Character {character_ownership.character} does not belong "
339
339
  "to owner corporation."
340
340
  )
341
- obj, _ = self.characters.get_or_create(character_ownership=character_ownership)
341
+ obj: OwnerCharacter = self.characters.get_or_create(
342
+ character_ownership=character_ownership
343
+ )[0]
344
+ obj.reset()
342
345
  return obj
343
346
 
344
- def characters_count(self) -> int:
347
+ def valid_characters_count(self) -> int:
345
348
  """Count of valid owner characters."""
346
- return self.characters.count()
349
+ return self.characters.filter(is_enabled=True).count()
347
350
 
348
351
  def has_sov(self, eve_solar_system: EveSolarSystem) -> bool:
349
352
  """Determine whether this owner has sov in the given solar system."""
@@ -351,42 +354,31 @@ class Owner(models.Model):
351
354
  eve_solar_system=eve_solar_system, corporation=self.corporation
352
355
  )
353
356
 
354
- def delete_character(
357
+ def disable_character(
355
358
  self,
356
359
  character: "OwnerCharacter",
357
- error: str,
358
- level: str = "warning",
360
+ reason: str,
359
361
  max_allowed_errors: int = 0,
360
362
  ) -> None:
361
- """Delete character and notify it's owner and admin about the reason
363
+ """Disable character and notify it's owner and admins about it.
362
364
 
363
365
  Args:
364
- - character: Character this error refers to
365
- - error: Error text
366
- - level: context level for the notification
367
- - max_error: how many errors are permitted before character is deleted
366
+ - character: Character to disable
367
+ - reason: User friendly reason for the deletion
368
+ - max_allowed_errors: Maximum number of allowed errors for this type of error
368
369
  """
369
370
  if character.error_count < max_allowed_errors:
370
- logger.warning(
371
- (
372
- "%s: Character encountered an error and will be deleted "
373
- "if this occurs more often (%d/%d): %s"
374
- ),
375
- character,
376
- character.error_count + 1,
377
- max_allowed_errors,
378
- error,
379
- )
380
- with transaction.atomic():
381
- character.error_count = F("error_count") + 1
382
- character.save(update_fields=["error_count"])
371
+ character.increase_error_count()
383
372
  return
384
373
 
385
- title = f"{__title__}: {self}: Invalid character has been removed"
374
+ character.disable(reason)
375
+
376
+ title = f"{__title__}: {self}: Character has been disabled"
377
+ level = "warning"
386
378
  message = (
387
- f"{character.character_ownership}: {error}\n"
388
- "The character has been removed. "
389
- "Please add a new character to restore the previous service level."
379
+ f"{character.character_ownership}: {reason}\n"
380
+ "This character caused too many errors and has been disabled. "
381
+ "Administrator action is required to resolve this issue."
390
382
  )
391
383
  notify(
392
384
  user=character.character_ownership.user,
@@ -394,15 +386,44 @@ class Owner(models.Model):
394
386
  message=message,
395
387
  level=level,
396
388
  )
397
- if self.characters.count() == 1:
389
+ if not self.valid_characters_count():
398
390
  message += (
399
391
  " This owner has no configured characters anymore "
400
392
  "and it's services are now down."
401
393
  )
402
394
  level = "danger"
395
+
403
396
  notify_admins(title=f"FYI: {title}", message=message, level=level)
397
+
398
+ def delete_character(self, character: "OwnerCharacter", reason: str) -> None:
399
+ """Delete character and notify it's owner and admin about the reason
400
+
401
+ Args:
402
+ - character: Character this error refers to
403
+ - reason: User friendly reason for the deletion
404
+ """
404
405
  character.delete()
405
406
 
407
+ title = f"{__title__}: {self}: Invalid character has been removed"
408
+ level = "warning"
409
+ message = (
410
+ f"{character.character_ownership}: {reason}\n"
411
+ "Your character is no longer valid for syncing this owner and has been removed. "
412
+ )
413
+ notify(
414
+ user=character.character_ownership.user,
415
+ title=title,
416
+ message=message,
417
+ level=level,
418
+ )
419
+ if not self.valid_characters_count():
420
+ message += (
421
+ " This owner has no valid characters anymore "
422
+ "and it's services are now down."
423
+ )
424
+ level = "danger"
425
+ notify_admins(title=f"FYI: {title}", message=message, level=level)
426
+
406
427
  def _rotate_character(
407
428
  self,
408
429
  character: "OwnerCharacter",
@@ -421,7 +442,7 @@ class Owner(models.Model):
421
442
  )
422
443
  try:
423
444
  minimum_time_between_rotations = max(
424
- rotate_characters.esi_cache_duration / self.characters.count(),
445
+ rotate_characters.esi_cache_duration / self.valid_characters_count(),
425
446
  60,
426
447
  )
427
448
  except ZeroDivisionError:
@@ -453,14 +474,18 @@ class Owner(models.Model):
453
474
  if rotate_characters
454
475
  else "notifications_last_used_at"
455
476
  )
456
- for character in self.characters.order_by(order_by_last_used):
477
+ enabled_characters: models.QuerySet[OwnerCharacter] = self.characters.filter(
478
+ is_enabled=True
479
+ ).order_by(order_by_last_used)
480
+ for character in enabled_characters:
457
481
  if (
458
482
  character.character_ownership.character.corporation_id
459
483
  != self.corporation.corporation_id
460
484
  ):
485
+ corporation_name = self.corporation.corporation_name
461
486
  self.delete_character(
462
487
  character=character,
463
- error="Character does no longer belong to the owner's corporation.",
488
+ reason=f"Character is no longer a member of {corporation_name}",
464
489
  )
465
490
  continue
466
491
 
@@ -469,17 +494,17 @@ class Owner(models.Model):
469
494
  ):
470
495
  self.delete_character(
471
496
  character=character,
472
- error="Character does not have sufficient permission to sync.",
497
+ reason="Character no longer has permission to sync",
473
498
  )
474
499
  continue
475
500
 
476
501
  token = character.valid_token()
477
502
  if not token:
478
- self.delete_character(
479
- character=character,
480
- error="Character has no valid token for sync.",
503
+ self.disable_character(
504
+ character=character, reason="No valid token found for character"
481
505
  )
482
506
  continue
507
+
483
508
  found_character = character
484
509
  break # leave the for loop if we have found a valid token
485
510
 
@@ -797,6 +822,13 @@ class Owner(models.Model):
797
822
 
798
823
  Return True when successful, else False.
799
824
  """
825
+ try:
826
+ character: OwnerCharacter = self.characters.get(
827
+ character_ownership__character__character_id=token.character_id
828
+ )
829
+ except ObjectDoesNotExist:
830
+ return False
831
+
800
832
  structures = []
801
833
  try:
802
834
  starbases_data = (
@@ -824,21 +856,11 @@ class Owner(models.Model):
824
856
  self._store_raw_data("starbases", structures)
825
857
 
826
858
  except HTTPForbidden:
827
- try:
828
- character = self.characters.get(
829
- character_ownership__character__character_id=token.character_id
830
- )
831
- except ObjectDoesNotExist:
832
- pass
833
- else:
834
- self.delete_character(
835
- character=character,
836
- error=(
837
- "Character is not a director or CEO and therefore "
838
- "can not fetch starbases."
839
- ),
840
- max_allowed_errors=STRUCTURES_ESI_DIRECTOR_ERROR_MAX_RETRIES,
841
- )
859
+ self.disable_character(
860
+ character=character,
861
+ reason=("This character is not a director or CEO"),
862
+ max_allowed_errors=STRUCTURES_ESI_DIRECTOR_ERROR_MAX_RETRIES,
863
+ )
842
864
  return False
843
865
 
844
866
  except OSError as ex:
@@ -849,6 +871,7 @@ class Owner(models.Model):
849
871
  structures_qs=self.structures.filter_starbases(),
850
872
  new_structures=structures,
851
873
  )
874
+ character.reset_error_counter()
852
875
  return True
853
876
 
854
877
  def _store_updates_for_starbases(self, token, structures):
@@ -1337,6 +1360,12 @@ class OwnerCharacter(models.Model):
1337
1360
  verbose_name=_("error count"),
1338
1361
  help_text="Count of ESI errors which happened with this character.",
1339
1362
  )
1363
+ is_enabled = models.BooleanField(
1364
+ default=True,
1365
+ verbose_name=_("is enabled"),
1366
+ help_text=_("Disabled characters are not used for syncing owners"),
1367
+ )
1368
+ disabled_reason = models.TextField(default="")
1340
1369
  created_at = models.DateTimeField(auto_now_add=True)
1341
1370
 
1342
1371
  class Meta:
@@ -1369,3 +1398,27 @@ class OwnerCharacter(models.Model):
1369
1398
  .require_valid()
1370
1399
  .first()
1371
1400
  )
1401
+
1402
+ def reset(self) -> None:
1403
+ """Resets a disabled owner character."""
1404
+ self.is_enabled = True
1405
+ self.disabled_reason = ""
1406
+ self.error_count = 0
1407
+ self.save()
1408
+
1409
+ def reset_error_counter(self) -> None:
1410
+ """Reset the error counter"""
1411
+ self.error_count = 0
1412
+ self.save(update_fields=["error_count"])
1413
+
1414
+ def disable(self, reason: str = "") -> None:
1415
+ """Disables a character."""
1416
+ self.is_enabled = False
1417
+ self.disabled_reason = reason
1418
+ self.save()
1419
+
1420
+ def increase_error_count(self):
1421
+ """Increase error count of this character by one."""
1422
+ with transaction.atomic():
1423
+ self.error_count = F("error_count") + 1
1424
+ self.save(update_fields=["error_count"])
@@ -240,6 +240,26 @@ class TestNotificationEmbedsGenerate(TestCase):
240
240
  self.assertEqual(discord_embed.footer.text, "Structures")
241
241
  self.assertIn("structures_logo.png", discord_embed.footer.icon_url)
242
242
 
243
+ def test_should_not_break_with_too_large_description(self):
244
+ # given
245
+ notification = Notification.objects.get(notification_id=1000000403)
246
+ notification_embed = NotificationBaseEmbed.create(notification)
247
+ notification_embed._description = "x" * 2049
248
+ # when
249
+ discord_embed = notification_embed.generate_embed()
250
+ # then
251
+ self.assertIsInstance(discord_embed, dhooks_lite.Embed)
252
+
253
+ def test_should_not_break_with_too_large_title(self):
254
+ # given
255
+ notification = Notification.objects.get(notification_id=1000000403)
256
+ notification_embed = NotificationBaseEmbed.create(notification)
257
+ notification_embed._title = "x" * 257
258
+ # when
259
+ discord_embed = notification_embed.generate_embed()
260
+ # then
261
+ self.assertIsInstance(discord_embed, dhooks_lite.Embed)
262
+
243
263
 
244
264
  class TestNotificationEmbedsClasses(NoSocketsTestCase):
245
265
  @classmethod
@@ -33,6 +33,7 @@ if "discord" in app_labels():
33
33
  super().setUpClass()
34
34
  load_eveuniverse()
35
35
  load_eve_entities()
36
+
36
37
  cls.group_1 = Group.objects.create(name="Dummy Group 1")
37
38
  cls.group_2 = Group.objects.create(name="Dummy Group 2")
38
39
  cls.owner = OwnerFactory()
@@ -140,3 +141,23 @@ if "discord" in app_labels():
140
141
  self.assertTrue(mock_import_discord.called)
141
142
  _, kwargs = mock_send_message.call_args
142
143
  self.assertFalse(re.search(r"(<@&\d+>)", kwargs["content"]))
144
+
145
+ def test_should_abort_when_content_is_too_large(
146
+ self, mock_send_message, mock_import_discord
147
+ ):
148
+ # given
149
+ mock_send_message.return_value = 1
150
+ mock_import_discord.return_value.objects.group_to_role.side_effect = (
151
+ self._my_group_to_role
152
+ )
153
+ webhook = WebhookFactory()
154
+ for i in range(286):
155
+ group = Group.objects.create(name=f"Group {i+1}")
156
+ webhook.ping_groups.add(group)
157
+ obj = clone_notification(
158
+ Notification.objects.get(notification_id=1000000509)
159
+ )
160
+ # when
161
+ result = obj.send_to_webhook(webhook)
162
+ # then
163
+ self.assertFalse(result)