aa-bb 3.2.7b4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. aa_bb/__init__.py +4 -0
  2. aa_bb/admin.py +627 -0
  3. aa_bb/app_settings.py +2375 -0
  4. aa_bb/apps.py +68 -0
  5. aa_bb/auth_hooks.py +227 -0
  6. aa_bb/celery.py +63 -0
  7. aa_bb/checks/__init__.py +8 -0
  8. aa_bb/checks/add_to_blacklist.py +190 -0
  9. aa_bb/checks/alliance_blacklist.py +21 -0
  10. aa_bb/checks/alpha_skills.json +757 -0
  11. aa_bb/checks/awox.py +383 -0
  12. aa_bb/checks/clone_state.py +305 -0
  13. aa_bb/checks/coalition_blacklist.py +19 -0
  14. aa_bb/checks/corp_changes.py +257 -0
  15. aa_bb/checks/cyno.py +387 -0
  16. aa_bb/checks/hostile_assets.py +311 -0
  17. aa_bb/checks/hostile_clones.py +298 -0
  18. aa_bb/checks/roles_and_tokens.py +219 -0
  19. aa_bb/checks/skills.json +659 -0
  20. aa_bb/checks/skills.py +303 -0
  21. aa_bb/checks/sus_contacts.py +301 -0
  22. aa_bb/checks/sus_contracts.py +290 -0
  23. aa_bb/checks/sus_mails.py +295 -0
  24. aa_bb/checks/sus_trans.py +420 -0
  25. aa_bb/checks_cb/__init__.py +7 -0
  26. aa_bb/checks_cb/hostile_assets.py +143 -0
  27. aa_bb/checks_cb/sus_contracts.py +322 -0
  28. aa_bb/checks_cb/sus_trans.py +388 -0
  29. aa_bb/esi_cache.py +39 -0
  30. aa_bb/esi_client.py +75 -0
  31. aa_bb/forms.py +52 -0
  32. aa_bb/locale/.gitkeep +0 -0
  33. aa_bb/management/__init__.py +0 -0
  34. aa_bb/management/commands/__init__.py +0 -0
  35. aa_bb/migrations/0001_initial.py +25 -0
  36. aa_bb/migrations/0002_bigbrotherconfig_userstatus.py +58 -0
  37. aa_bb/migrations/0003_alter_bigbrotherconfig_pingroleid.py +18 -0
  38. aa_bb/migrations/0004_alter_bigbrotherconfig_is_active_and_more.py +23 -0
  39. aa_bb/migrations/0005_alter_bigbrotherconfig_hostile_alliances.py +18 -0
  40. aa_bb/migrations/0006_alter_bigbrotherconfig_pingroleid.py +18 -0
  41. aa_bb/migrations/0007_alter_general_options.py +17 -0
  42. aa_bb/migrations/0008_alliance_names_corporation_names.py +41 -0
  43. aa_bb/migrations/0009_userstatus_sus_contacts_userstatus_sus_contracts_and_more.py +38 -0
  44. aa_bb/migrations/0010_alter_userstatus_awox_kill_links_and_more.py +48 -0
  45. aa_bb/migrations/0011_character_names.py +27 -0
  46. aa_bb/migrations/0012_id_types.py +27 -0
  47. aa_bb/migrations/0013_bigbrotherconfig_mail_keywords.py +18 -0
  48. aa_bb/migrations/0014_processedcontract_processedmail_and_more.py +74 -0
  49. aa_bb/migrations/0015_processedtransaction_sustransactionnote.py +42 -0
  50. aa_bb/migrations/0016_warmprogress.py +23 -0
  51. aa_bb/migrations/0017_entityinfocache.py +27 -0
  52. aa_bb/migrations/0018_userstatus_cyno_userstatus_has_skills_and_more.py +28 -0
  53. aa_bb/migrations/0019_bigbrotherconfig_whitelist_alliances_and_more.py +23 -0
  54. aa_bb/migrations/0020_messages_bigbrotherconfig_are_daily_messages_active_and_more.py +35 -0
  55. aa_bb/migrations/0021_alter_messages_id_alter_messages_text.py +23 -0
  56. aa_bb/migrations/0022_messages_sent_in_cycle.py +18 -0
  57. aa_bb/migrations/0023_optmessages1_optmessages2_optmessages3_optmessages4_and_more.py +103 -0
  58. aa_bb/migrations/0024_bigbrotherconfig_dailyschedule_and_more.py +45 -0
  59. aa_bb/migrations/0025_alter_messages_options_alter_optmessages1_options_and_more.py +37 -0
  60. aa_bb/migrations/0026_alter_general_options_alter_bigbrotherconfig_id_and_more.py +97 -0
  61. aa_bb/migrations/0027_alter_general_options_bigbrotherconfig_is_loa_active_and_more.py +27 -0
  62. aa_bb/migrations/0028_alter_bigbrotherconfig_is_loa_active.py +18 -0
  63. aa_bb/migrations/0029_leaverequest_main_character.py +18 -0
  64. aa_bb/migrations/0030_alter_general_options.py +17 -0
  65. aa_bb/migrations/0031_bigbrotherconfig_loa_max_logoff_days_and_more.py +88 -0
  66. aa_bb/migrations/0032_alter_leaverequest_status.py +18 -0
  67. aa_bb/migrations/0033_messagetype_bigbrotherconfig_pingroleid2_and_more.py +45 -0
  68. aa_bb/migrations/0034_rename_last_updated_userstatus_updated.py +18 -0
  69. aa_bb/migrations/0035_alter_userstatus_options.py +17 -0
  70. aa_bb/migrations/0036_alter_general_options.py +17 -0
  71. aa_bb/migrations/0037_corpstatus.py +32 -0
  72. aa_bb/migrations/0038_bigbrotherconfig_ignored_corporations.py +18 -0
  73. aa_bb/migrations/0039_alter_bigbrotherconfig_ignored_corporations.py +18 -0
  74. aa_bb/migrations/0040_sovereigntymapcache_corporationinfocache_and_more.py +49 -0
  75. aa_bb/migrations/0041_bigbrotherconfig_is_warmer_active.py +18 -0
  76. aa_bb/migrations/0042_alter_general_options_and_more.py +23 -0
  77. aa_bb/migrations/0043_bigbrotherconfig_bb_member_states_and_more.py +24 -0
  78. aa_bb/migrations/0044_bigbrotherconfig_character_scopes_and_more.py +23 -0
  79. aa_bb/migrations/0045_userstatus_sp_age_ratio_result.py +18 -0
  80. aa_bb/migrations/0046_bigbrotherconfig_member_corporations.py +18 -0
  81. aa_bb/migrations/0047_bigbrotherconfig_member_alliances.py +18 -0
  82. aa_bb/migrations/0048_characteraccountstate.py +21 -0
  83. aa_bb/migrations/0049_userstatus_clone_status.py +18 -0
  84. aa_bb/migrations/0050_alter_general_options_and_more.py +22 -0
  85. aa_bb/migrations/0051_monthlypapstats.py +34 -0
  86. aa_bb/migrations/0052_papsconfig_delete_monthlypapstats.py +30 -0
  87. aa_bb/migrations/0053_alter_papsconfig_corp_modifier_and_more.py +38 -0
  88. aa_bb/migrations/0054_alter_general_options.py +17 -0
  89. aa_bb/migrations/0055_papsconfig_group_paps_papsconfig_group_paps_modifier.py +24 -0
  90. aa_bb/migrations/0056_alter_papsconfig_group_paps.py +19 -0
  91. aa_bb/migrations/0057_papsconfig_excluded_groups_papsconfig_excluded_users_and_more.py +31 -0
  92. aa_bb/migrations/0058_papsconfig_excluded_groups_get_paps_and_more.py +30 -0
  93. aa_bb/migrations/0059_alter_papsconfig_excluded_groups_get_paps.py +18 -0
  94. aa_bb/migrations/0060_papsconfig_cap_group_papsconfig_cap_group_paps_and_more.py +49 -0
  95. aa_bb/migrations/0061_remove_papsconfig_cap_group_and_more.py +42 -0
  96. aa_bb/migrations/0062_complianceticket.py +28 -0
  97. aa_bb/migrations/0063_alter_complianceticket_user.py +21 -0
  98. aa_bb/migrations/0064_alter_complianceticket_reason.py +18 -0
  99. aa_bb/migrations/0065_alter_complianceticket_reason_and_more.py +26 -0
  100. aa_bb/migrations/0066_tickettoolconfig_papcompliance.py +52 -0
  101. aa_bb/migrations/0067_remove_tickettoolconfig_category_id_and_more.py +21 -0
  102. aa_bb/migrations/0068_tickettoolconfig_category_id_and_more.py +23 -0
  103. aa_bb/migrations/0069_tickettoolconfig_ticket_counter.py +18 -0
  104. aa_bb/migrations/0070_remove_tickettoolconfig_role_id_and_more.py +54 -0
  105. aa_bb/migrations/0071_tickettoolconfig_role_id.py +18 -0
  106. aa_bb/migrations/0072_alter_complianceticket_reason.py +18 -0
  107. aa_bb/migrations/0073_tickettoolconfig_starting_pap_compliance.py +18 -0
  108. aa_bb/migrations/0074_alter_bigbrotherconfig_hostile_alliances.py +18 -0
  109. aa_bb/migrations/0075_bbupdatestate_tickettoolconfig_afk_check_frequency_and_more.py +49 -0
  110. aa_bb/migrations/0076_id_types_last_accessed_frequentcorpchangescache_and_more.py +74 -0
  111. aa_bb/migrations/0077_complianceticket_ticket_id_and_more.py +28 -0
  112. aa_bb/migrations/0078_tickettoolconfig_awox_monitor_enabled.py +18 -0
  113. aa_bb/migrations/0079_helptext_guidance.py +57 -0
  114. aa_bb/migrations/0080_bigbrotherconfig_dlc_flags.py +65 -0
  115. aa_bb/migrations/0081_remove_papsconfig_imp_modifier_and_more.py +56 -0
  116. aa_bb/migrations/0082_remove_bigbrotherconfig_token_and_more.py +82 -0
  117. aa_bb/migrations/0083_alter_bigbrotherredditmessage_options_and_more.py +71 -0
  118. aa_bb/migrations/0084_bigbrotherconfig_consider_all_structures_hostile_and_more.py +43 -0
  119. aa_bb/migrations/0085_bigbrotherconfig_awox_notify_and_more.py +33 -0
  120. aa_bb/migrations/0086_bigbrotherconfig_are_recurring_stats_active_and_more.py +54 -0
  121. aa_bb/migrations/0087_bigbrotherconfig_dlc_are_recurring_stats_active.py +18 -0
  122. aa_bb/migrations/0088_bigbrotherconfig_cyno_notify.py +18 -0
  123. aa_bb/migrations/0089_bigbrotherconfig_asset_notify_and_more.py +58 -0
  124. aa_bb/migrations/0090_alter_papsconfig_options_and_more.py +488 -0
  125. aa_bb/migrations/0091_remove_bigbrotherconfig_dlc_are_recurring_stats_active_and_more.py +62 -0
  126. aa_bb/migrations/0092_userstatus_baseline_initialized.py +28 -0
  127. aa_bb/migrations/0093_eveitemprice_delete_bigbrotherredditmessage_and_more.py +154 -0
  128. aa_bb/migrations/0094_bigbrotherconfig_corp_compliance_webhook_and_more.py +28 -0
  129. aa_bb/migrations/0095_alter_bigbrotherconfig_limit_to_main_corp.py +18 -0
  130. aa_bb/migrations/0096_bigbrotherconfig_clone_state_notify_and_more.py +23 -0
  131. aa_bb/migrations/0097_bigbrotherconfig_hostile_everyone_else.py +18 -0
  132. aa_bb/migrations/0098_bigbrotherconfig_exclude_high_sec_and_more.py +28 -0
  133. aa_bb/migrations/0099_tickettoolconfig_hr_forum_webhook.py +18 -0
  134. aa_bb/migrations/0100_tickettoolconfig_use_forum_threads.py +18 -0
  135. aa_bb/migrations/0101_userstatus_compliance_forum_thread_id.py +114 -0
  136. aa_bb/migrations/0102_remove_tickettoolconfig_use_forum_threads_and_more.py +67 -0
  137. aa_bb/migrations/0103_tickettoolconfig_afk_check_include_user_and_more.py +58 -0
  138. aa_bb/migrations/0104_alter_bigbrotherconfig_is_active.py +24 -0
  139. aa_bb/migrations/0105_remove_tickettoolconfig_compliance_filter.py +52 -0
  140. aa_bb/migrations/0106_bigbrotherconfig_consider_lowsec_hostile.py +18 -0
  141. aa_bb/migrations/0107_remove_tickettoolconfig_role_id_and_more.py +36 -0
  142. aa_bb/migrations/__init__.py +0 -0
  143. aa_bb/models.py +2191 -0
  144. aa_bb/signals.py +64 -0
  145. aa_bb/static/aa_bb/.gitkeep +0 -0
  146. aa_bb/static/aa_bb/js/admin_market_toggle.js +21 -0
  147. aa_bb/static/aa_bb/js/admin_ticket_type_toggle.js +44 -0
  148. aa_bb/tasks.py +1467 -0
  149. aa_bb/tasks_bot.py +880 -0
  150. aa_bb/tasks_cb.py +950 -0
  151. aa_bb/tasks_ct.py +372 -0
  152. aa_bb/tasks_other.py +255 -0
  153. aa_bb/tasks_tickets.py +662 -0
  154. aa_bb/tasks_utils.py +342 -0
  155. aa_bb/templates/aa_bb/base.html +25 -0
  156. aa_bb/templates/aa_bb/disabled.html +20 -0
  157. aa_bb/templates/aa_bb/index.html +555 -0
  158. aa_bb/templates/aa_bb/ticket_list.html +73 -0
  159. aa_bb/templates/aa_bb/ticket_view.html +117 -0
  160. aa_bb/templates/aa_cb/base.html +26 -0
  161. aa_bb/templates/aa_cb/disabled.html +20 -0
  162. aa_bb/templates/aa_cb/index.html +398 -0
  163. aa_bb/templates/faq/base.html +19 -0
  164. aa_bb/templates/faq/cards.html +115 -0
  165. aa_bb/templates/faq/faq.html +531 -0
  166. aa_bb/templates/faq/menu.html +30 -0
  167. aa_bb/templates/faq/modules.html +66 -0
  168. aa_bb/templates/faq/settings_bigbrother.html +355 -0
  169. aa_bb/templates/faq/settings_nav.html +49 -0
  170. aa_bb/templates/faq/settings_paps.html +132 -0
  171. aa_bb/templates/faq/settings_stats.html +116 -0
  172. aa_bb/templates/faq/settings_tickets.html +332 -0
  173. aa_bb/templates/loa/_loa_subtabs.html +17 -0
  174. aa_bb/templates/loa/admin.html +113 -0
  175. aa_bb/templates/loa/base.html +22 -0
  176. aa_bb/templates/loa/disabled.html +19 -0
  177. aa_bb/templates/loa/index.html +62 -0
  178. aa_bb/templates/loa/menu.html +27 -0
  179. aa_bb/templates/loa/request.html +81 -0
  180. aa_bb/templates/paps/base.html +21 -0
  181. aa_bb/templates/paps/disabled.html +19 -0
  182. aa_bb/templates/paps/history.html +25 -0
  183. aa_bb/templates/paps/index.html +86 -0
  184. aa_bb/templates/paps/menu.html +20 -0
  185. aa_bb/tests/__init__.py +1 -0
  186. aa_bb/tests/test_clone_state_logic.py +75 -0
  187. aa_bb/tests/test_performance.py +129 -0
  188. aa_bb/tests/test_price_timer.py +62 -0
  189. aa_bb/tests/test_sus_contacts.py +79 -0
  190. aa_bb/tests/test_sus_trans.py +224 -0
  191. aa_bb/tests/test_task_setup.py +83 -0
  192. aa_bb/tests/test_tasks_notifications.py +70 -0
  193. aa_bb/tests/test_tasks_recurring_stats.py +92 -0
  194. aa_bb/tests/test_views.py +203 -0
  195. aa_bb/urls.py +65 -0
  196. aa_bb/urls_cb.py +27 -0
  197. aa_bb/urls_loa.py +14 -0
  198. aa_bb/urls_paps.py +10 -0
  199. aa_bb/views.py +1423 -0
  200. aa_bb/views_cb.py +776 -0
  201. aa_bb/views_faq.py +581 -0
  202. aa_bb/views_paps.py +342 -0
  203. aa_bb-3.2.7b4.dist-info/METADATA +445 -0
  204. aa_bb-3.2.7b4.dist-info/RECORD +206 -0
  205. aa_bb-3.2.7b4.dist-info/WHEEL +4 -0
  206. aa_bb-3.2.7b4.dist-info/licenses/LICENSE +7 -0
aa_bb/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ """Initialize the app"""
2
+
3
+ __version__ = "3.2.7b4"
4
+ __title__ = "BigBrother"
aa_bb/admin.py ADDED
@@ -0,0 +1,627 @@
1
+ """
2
+ Admin registrations for every BigBrother-related model.
3
+
4
+ Most models are singletons that gate optional modules. The helpers below
5
+ ensure their admin entries only appear when the relevant feature is enabled
6
+ and prevent accidental multi-row creation of what should be one-off configs.
7
+ """
8
+
9
+ from solo.admin import SingletonModelAdmin
10
+
11
+ from django.contrib import admin
12
+ from .app_settings import afat_active, discordbot_active, charlink_active
13
+ from django.contrib.admin.sites import NotRegistered
14
+
15
+ from .models import (
16
+ BigBrotherConfig,
17
+ Messages,
18
+ OptMessages1,
19
+ OptMessages2,
20
+ OptMessages3,
21
+ OptMessages4,
22
+ OptMessages5,
23
+ UserStatus,
24
+ WarmProgress,
25
+ PapsConfig,
26
+ RecurringStatsConfig,
27
+ AA_CONTACTS_INSTALLED,
28
+ TicketToolConfig,
29
+ PapCompliance,
30
+ LeaveRequest,
31
+ ComplianceTicket,
32
+ ComplianceThread,
33
+ EveItemPrice,
34
+ )
35
+
36
+ @admin.register(BigBrotherConfig)
37
+ class BB_ConfigAdmin(SingletonModelAdmin):
38
+ fieldsets = (
39
+ (
40
+ "Core Activation",
41
+ {
42
+ "fields": (
43
+ "is_active",
44
+ "is_warmer_active",
45
+ "is_loa_active",
46
+ "is_paps_active",
47
+ "are_daily_messages_active",
48
+ "are_recurring_stats_active",
49
+ "are_opt_messages1_active",
50
+ "are_opt_messages2_active",
51
+ "are_opt_messages3_active",
52
+ "are_opt_messages4_active",
53
+ "are_opt_messages5_active",
54
+ "loa_max_logoff_days",
55
+ )
56
+ },
57
+ ),
58
+ (
59
+ "Notifications",
60
+ {
61
+ "fields": (
62
+ "ct_notify",
63
+ "awox_notify",
64
+ "cyno_notify",
65
+ "sp_inject_notify",
66
+ "clone_notify",
67
+ "clone_state_notify",
68
+ "asset_notify",
69
+ "contact_notify",
70
+ "contract_notify",
71
+ "mail_notify",
72
+ "transaction_notify",
73
+ "show_market_transactions",
74
+ "new_user_notify",
75
+ ),
76
+ },
77
+ ),
78
+ (
79
+ "Update Performance",
80
+ {
81
+ "fields": (
82
+ "clone_state_always_recheck",
83
+ "update_stagger_seconds",
84
+ "update_cache_ttl_hours",
85
+ "update_maintenance_window_start",
86
+ "update_maintenance_window_end",
87
+ "update_backlog_threshold",
88
+ "update_backlog_notify",
89
+ ),
90
+ },
91
+ ),
92
+ (
93
+ "Market Transaction Settings",
94
+ {
95
+ "classes": ("market-transaction-settings-fieldset",),
96
+ "fields": (
97
+ "market_transactions_show_major_hubs",
98
+ "market_transactions_show_secondary_hubs",
99
+ "market_transactions_excluded_systems",
100
+ "market_transactions_threshold_alert",
101
+ "market_transactions_threshold_percent",
102
+ "market_transactions_price_method",
103
+ "market_transactions_janice_api_key",
104
+ "market_transactions_fuzzwork_station_id",
105
+ "market_transactions_price_instant",
106
+ "market_transactions_price_max_age",
107
+ )
108
+ },
109
+ ),
110
+ (
111
+ "Blacklist Settings",
112
+ {
113
+ "fields": (
114
+ "alliance_blacklist_url",
115
+ "external_blacklist_url",
116
+ )
117
+ },
118
+ ),
119
+ (
120
+ "Ping / Messaging Roles",
121
+ {
122
+ "fields": (
123
+ "pingroleID",
124
+ "pingroleID2",
125
+ "pingrole1_messages",
126
+ "pingrole2_messages",
127
+ "here_messages",
128
+ "everyone_messages",
129
+ )
130
+ },
131
+ ),
132
+ (
133
+ "Webhooks",
134
+ {
135
+ "fields": (
136
+ "webhook",
137
+ "loawebhook",
138
+ "dailywebhook",
139
+ "optwebhook1",
140
+ "optwebhook2",
141
+ "optwebhook3",
142
+ "optwebhook4",
143
+ "optwebhook5",
144
+ "user_compliance_webhook",
145
+ "corp_compliance_webhook",
146
+ "stats_webhook",
147
+ )
148
+ },
149
+ ),
150
+ (
151
+ "Schedules",
152
+ {
153
+ "fields": (
154
+ "dailyschedule",
155
+ "optschedule1",
156
+ "optschedule2",
157
+ "optschedule3",
158
+ "optschedule4",
159
+ "optschedule5",
160
+ "stats_schedule",
161
+ ),
162
+ },
163
+ ),
164
+ (
165
+ "User State & Membership",
166
+ {
167
+ "fields": (
168
+ "limit_to_main_corp",
169
+ "bb_guest_states",
170
+ "bb_member_states",
171
+ "member_corporations",
172
+ "member_alliances",
173
+ )
174
+ },
175
+ ),
176
+ (
177
+ "Hostile / Whitelist Rules",
178
+ {
179
+ "fields": (
180
+ "hostile_alliances",
181
+ "hostile_corporations",
182
+ "hostile_everyone_else",
183
+ "whitelist_alliances",
184
+ "whitelist_corporations",
185
+ "ignored_corporations",
186
+ "consider_nullsec_hostile",
187
+ "consider_lowsec_hostile",
188
+ "consider_all_structures_hostile",
189
+ "consider_npc_stations_hostile",
190
+ "excluded_systems",
191
+ "excluded_stations",
192
+ "exclude_high_sec",
193
+ "exclude_low_sec",
194
+ "hostile_assets_ships_only",
195
+ # aa-contacts import (conditionally add fields)
196
+ *(
197
+ (
198
+ "auto_import_contacts_enabled",
199
+ "contacts_source_alliances",
200
+ "contacts_source_corporations",
201
+ "contacts_handle_neutrals",
202
+ )
203
+ if AA_CONTACTS_INSTALLED
204
+ else ()
205
+ ),
206
+ )
207
+ },
208
+ ),
209
+ (
210
+ "Scopes",
211
+ {
212
+ "classes": ("collapse",),
213
+ "fields": (
214
+ "character_scopes",
215
+ "corporation_scopes",
216
+ ),
217
+ },
218
+ ),
219
+ (
220
+ "Main Corp / Alliance",
221
+ {
222
+ "fields": (
223
+ "main_corporation_id",
224
+ "main_corporation",
225
+ "main_alliance_id",
226
+ "main_alliance",
227
+ ),
228
+ },
229
+ ),
230
+ )
231
+ """Singleton config for the core BigBrother module."""
232
+ readonly_fields = (
233
+ "main_corporation",
234
+ "main_alliance",
235
+ "main_corporation_id",
236
+ "main_alliance_id",
237
+ "update_last_dispatch_count",
238
+ )
239
+ filter_horizontal = (
240
+ "pingrole1_messages",
241
+ "pingrole2_messages",
242
+ "here_messages",
243
+ "everyone_messages",
244
+ "bb_guest_states",
245
+ "bb_member_states",
246
+ # aa-contacts M2M (only if installed)
247
+ *(
248
+ ("contacts_source_alliances", "contacts_source_corporations")
249
+ if AA_CONTACTS_INSTALLED
250
+ else ()
251
+ ),
252
+ )
253
+
254
+ class Media:
255
+ js = ("aa_bb/js/admin_market_toggle.js",)
256
+
257
+ def has_add_permission(self, request):
258
+ """Prevent duplicate singleton rows."""
259
+ if BigBrotherConfig.objects.exists(): # Disallow when a config already exists.
260
+ return False
261
+ return super().has_add_permission(request)
262
+
263
+ def has_delete_permission(self, request, obj=None):
264
+ """Always allow deleting to keep parity with default behavior."""
265
+ return True
266
+
267
+
268
+ @admin.register(PapsConfig)
269
+ class PapsConfigAdmin(SingletonModelAdmin):
270
+ """Controls PAP multipliers/thresholds; singleton per installation."""
271
+ filter_horizontal = (
272
+ "group_paps",
273
+ "excluded_groups",
274
+ "excluded_users",
275
+ "excluded_users_paps",
276
+ )
277
+
278
+ def has_add_permission(self, request):
279
+ """Prevent duplicate PAP config entries."""
280
+ if PapsConfig.objects.exists(): # Disallow singleton duplication.
281
+ return False
282
+ return super().has_add_permission(request)
283
+
284
+ def has_delete_permission(self, request, obj=None):
285
+ """Allow deletes so admins can rebuild the configuration."""
286
+ return True
287
+
288
+
289
+ @admin.register(TicketToolConfig)
290
+ class TicketToolConfigAdmin(SingletonModelAdmin):
291
+ """Ticket automation thresholds + templates."""
292
+ readonly_fields = ("ticket_counter",)
293
+ filter_horizontal = (
294
+ "excluded_users",
295
+ )
296
+
297
+ def get_fieldsets(self, request, obj=None):
298
+ fieldsets = [
299
+ (None, {
300
+ 'fields': ('ticket_type', 'role_id', 'hr_forum_webhook', 'Forum_Channel_ID', 'ticket_counter', 'excluded_users')
301
+ }),
302
+ ]
303
+
304
+ # Corp Compliance Check - only show compliance_filter if charlink is installed
305
+ corp_check_fields = []
306
+ if charlink_active():
307
+ corp_check_fields.append('compliance_filter')
308
+ corp_check_fields.extend(['corp_check_enabled', 'corp_check_include_user', 'corp_check', 'corp_check_frequency', 'corp_check_reason', 'corp_check_reminder'])
309
+
310
+ fieldsets.append(('Corp Compliance Check', {
311
+ 'fields': tuple(corp_check_fields)
312
+ }))
313
+
314
+ fieldsets.extend([
315
+ ('Inactivity Check', {
316
+ 'fields': ('afk_check_enabled', 'afk_check_include_user', 'Max_Afk_Days', 'afk_check', 'afk_check_frequency', 'afk_check_reason', 'afk_check_reminder')
317
+ }),
318
+ ('Discord Inactivity Check', {
319
+ 'fields': ('discord_inactivity_enabled', 'discord_inactivity_include_user', 'discord_inactivity_days', 'discord_inactivity_reason')
320
+ }),
321
+ ('Character Removal Check', {
322
+ 'fields': ('char_removed_enabled', 'char_removed_include_user', 'char_removed_reason')
323
+ }),
324
+ ('AWOX Check', {
325
+ 'fields': ('awox_monitor_enabled', 'awox_kill_include_user', 'awox_kill_reason')
326
+ }),
327
+ ])
328
+
329
+ if discordbot_active():
330
+ fieldsets.insert(1, ('Private Channel Settings (Bot)', {
331
+ 'fields': ('Category_ID', 'role_id')
332
+ }))
333
+
334
+ # Find the index of Inactivity Check to insert Discord Link Check after it
335
+ idx = 0
336
+ for i, (name, _) in enumerate(fieldsets):
337
+ if name == 'Inactivity Check':
338
+ idx = i + 1
339
+ break
340
+
341
+ fieldsets.insert(idx, ('Discord Link Check', {
342
+ 'fields': ('discord_check_enabled', 'discord_check', 'discord_check_frequency', 'discord_check_reason', 'discord_check_reminder')
343
+ }))
344
+
345
+ if afat_active():
346
+ fieldsets.append(('PAP Compliance Check', {
347
+ 'fields': ('paps_check_enabled', 'paps_check_include_user', 'max_months_without_pap_compliance', 'starting_pap_compliance', 'paps_check', 'paps_check_frequency', 'paps_check_reason', 'paps_check_reminder')
348
+ }))
349
+
350
+ return fieldsets
351
+
352
+ class Media:
353
+ js = ("aa_bb/js/admin_ticket_type_toggle.js",)
354
+
355
+ def get_form(self, request, obj=None, **kwargs):
356
+ from django import forms
357
+ form = super().get_form(request, obj, **kwargs)
358
+
359
+ # Make role_id a textarea
360
+ if 'role_id' in form.base_fields:
361
+ form.base_fields['role_id'].widget = forms.Textarea(attrs={'rows': 3, 'cols': 40})
362
+
363
+ if not discordbot_active():
364
+ from .models import TicketToolConfig
365
+ # Restrict choices if bot is not active
366
+ allowed_choices = [
367
+ (TicketToolConfig.TICKET_TYPE_FORUM_THREAD, 'Public Forum Threads (Webhook)'),
368
+ (TicketToolConfig.TICKET_TYPE_AUTH_ONLY, 'Auth Only (No Discord)'),
369
+ ]
370
+ form.base_fields['ticket_type'].choices = allowed_choices
371
+ return form
372
+
373
+ def has_add_permission(self, request):
374
+ """Prevent duplicate ticket config entries."""
375
+ if TicketToolConfig.objects.exists(): # Ticket config should remain singleton.
376
+ return False
377
+ return super().has_add_permission(request)
378
+
379
+ def has_delete_permission(self, request, obj=None):
380
+ """Allow deletes when operators need to reset settings."""
381
+ return True
382
+
383
+
384
+
385
+
386
+ @admin.register(EveItemPrice)
387
+ class EveItemPriceAdmin(admin.ModelAdmin):
388
+ list_display = ("eve_type_id", "buy", "sell", "updated")
389
+ search_fields = ("eve_type_id",)
390
+
391
+
392
+ @admin.register(Messages)
393
+ class DailyMessageConfig(admin.ModelAdmin):
394
+ """Standard daily webhook messages rotated each cycle."""
395
+ search_fields = ["text"]
396
+ list_display = ["text", "sent_in_cycle"]
397
+
398
+
399
+ @admin.register(OptMessages1)
400
+ class OptMessage1Config(admin.ModelAdmin):
401
+ """Optional webhook stream #1."""
402
+ search_fields = ["text"]
403
+ list_display = ["text", "sent_in_cycle"]
404
+
405
+
406
+ @admin.register(OptMessages2)
407
+ class OptMessage2Config(admin.ModelAdmin):
408
+ """Optional webhook stream #2."""
409
+ search_fields = ["text"]
410
+ list_display = ["text", "sent_in_cycle"]
411
+
412
+
413
+ @admin.register(OptMessages3)
414
+ class OptMessage3Config(admin.ModelAdmin):
415
+ """Optional webhook stream #3."""
416
+ search_fields = ["text"]
417
+ list_display = ["text", "sent_in_cycle"]
418
+
419
+
420
+ @admin.register(OptMessages4)
421
+ class OptMessage4Config(admin.ModelAdmin):
422
+ """Optional webhook stream #4."""
423
+ search_fields = ["text"]
424
+ list_display = ["text", "sent_in_cycle"]
425
+
426
+
427
+ @admin.register(OptMessages5)
428
+ class OptMessage5Config(admin.ModelAdmin):
429
+ """Optional webhook stream #5."""
430
+ search_fields = ["text"]
431
+ list_display = ["text", "sent_in_cycle"]
432
+
433
+
434
+ @admin.register(WarmProgress)
435
+ class WarmProgressConfig(admin.ModelAdmin):
436
+ """Shows which users the cache warmer has processed recently."""
437
+ list_display = ["user_main", "updated"]
438
+
439
+
440
+ @admin.register(UserStatus)
441
+ class UserStatusConfig(admin.ModelAdmin):
442
+ """Simple heartbeat for per-user card status."""
443
+ list_display = ["user", "updated"]
444
+
445
+
446
+ class ReasonFilter(admin.SimpleListFilter):
447
+ title = 'reason'
448
+ parameter_name = 'reason'
449
+
450
+ def lookups(self, request, model_admin):
451
+ from .models import ComplianceTicket
452
+ reasons = list(ComplianceTicket.REASONS)
453
+ if not afat_active():
454
+ reasons = [r for r in reasons if r[0] != "paps_check"]
455
+ if not discordbot_active():
456
+ reasons = [r for r in reasons if r[0] != "discord_check"]
457
+ return reasons
458
+
459
+ def queryset(self, request, queryset):
460
+ if self.value():
461
+ return queryset.filter(reason=self.value())
462
+ return queryset
463
+
464
+
465
+ @admin.register(ComplianceTicket)
466
+ class ComplianceTicketConfig(admin.ModelAdmin):
467
+ """History of tickets issued by the automation layer."""
468
+ list_display = ["user", "ticket_id", "reason", "is_resolved", "is_exception"]
469
+ list_filter = ["is_resolved", "is_exception", ReasonFilter]
470
+ readonly_fields = ["created_at"]
471
+
472
+ actions = ["mark_as_exception", "clear_exception", "mark_as_resolved", "mark_as_open"]
473
+
474
+ def mark_as_exception(self, request, queryset):
475
+ """Mark selected tickets as exceptions."""
476
+ count = queryset.update(is_exception=True, exception_reason=f"Marked as exception by {request.user.username}")
477
+ self.message_user(request, f"{count} ticket(s) marked as exception.")
478
+ mark_as_exception.short_description = "Mark selected tickets as exception"
479
+
480
+ def clear_exception(self, request, queryset):
481
+ """Clear exception status from selected tickets."""
482
+ count = queryset.update(is_exception=False, exception_reason=None)
483
+ self.message_user(request, f"{count} ticket(s) exception status cleared.")
484
+ clear_exception.short_description = "Clear exception status"
485
+
486
+ def mark_as_resolved(self, request, queryset):
487
+ """Mark selected tickets as resolved."""
488
+ count = queryset.update(is_resolved=True)
489
+ self.message_user(request, f"{count} ticket(s) marked as resolved.")
490
+ mark_as_resolved.short_description = "Mark selected tickets as resolved"
491
+
492
+ def mark_as_open(self, request, queryset):
493
+ """Mark selected tickets as open."""
494
+ count = queryset.update(is_resolved=False, is_exception=False)
495
+ self.message_user(request, f"{count} ticket(s) marked as open.")
496
+ mark_as_open.short_description = "Mark selected tickets as open"
497
+
498
+ def get_queryset(self, request):
499
+ qs = super().get_queryset(request)
500
+ if not afat_active():
501
+ qs = qs.exclude(reason="paps_check")
502
+ if not discordbot_active():
503
+ qs = qs.exclude(reason="discord_check")
504
+ return qs
505
+
506
+
507
+ @admin.register(ComplianceThread)
508
+ class ComplianceThreadAdmin(admin.ModelAdmin):
509
+ """Mapping of user/reason to Discord thread IDs."""
510
+ list_display = ["user", "reason", "thread_id"]
511
+ list_filter = [ReasonFilter]
512
+
513
+ def get_queryset(self, request):
514
+ qs = super().get_queryset(request)
515
+ if not afat_active():
516
+ qs = qs.exclude(reason="paps_check")
517
+ if not discordbot_active():
518
+ qs = qs.exclude(reason="discord_check")
519
+ return qs
520
+
521
+
522
+ @admin.register(LeaveRequest)
523
+ class LeaveRequestConfig(admin.ModelAdmin):
524
+ """Expose LeaveRequest records to staff when LoA is enabled."""
525
+ list_display = ["main_character", "start_date", "end_date", "reason", "status"]
526
+
527
+
528
+ @admin.register(PapCompliance)
529
+ class PapComplianceConfig(admin.ModelAdmin):
530
+ """Shows the most recent PAP compliance calculation per user."""
531
+ search_fields = ["user_profile"]
532
+ list_display = ["user_profile", "pap_compliant"]
533
+
534
+
535
+ @admin.register(RecurringStatsConfig)
536
+ class RecurringStatsConfigAdmin(SingletonModelAdmin):
537
+ fieldsets = (
538
+ (
539
+ "General",
540
+ {
541
+ "fields": ("enabled",),
542
+ },
543
+ ),
544
+ (
545
+ "States",
546
+ {
547
+ "fields": ("states",),
548
+ "description": "Select which states you want broken out (Member, Blue, Alumni, etc.).",
549
+ },
550
+ ),
551
+ (
552
+ "Included Stats",
553
+ {
554
+ "fields": (
555
+ "include_auth_users",
556
+ "include_discord_users",
557
+ "include_mumble_users",
558
+ "include_characters",
559
+ "include_corporations",
560
+ "include_alliances",
561
+ "include_tokens",
562
+ "include_unique_tokens",
563
+ "include_character_audits",
564
+ "include_corporation_audits",
565
+ ),
566
+ },
567
+ ),
568
+ (
569
+ "Internal",
570
+ {
571
+ "fields": ("last_run_at", "last_snapshot"),
572
+ "classes": ("collapse",),
573
+ },
574
+ ),
575
+ )
576
+
577
+ filter_horizontal = ("states",)
578
+ readonly_fields = ("last_run_at", "last_snapshot")
579
+
580
+ if not afat_active():
581
+ for _m in (PapsConfig, PapCompliance):
582
+ try:
583
+ admin.site.unregister(_m)
584
+ except NotRegistered:
585
+ pass
586
+
587
+ _PAP_OBJECT_NAMES = {"PapsConfig", "PapCompliance"}
588
+ _MARKET_OBJECT_NAMES = {"EveItemPrice", "ProcessedTransaction", "SusTransactionNote"}
589
+ _ORIG_GET_APP_LIST = admin.site.get_app_list
590
+
591
+
592
+ def _filtered_get_app_list(request, app_label=None):
593
+ app_list = _ORIG_GET_APP_LIST(request, app_label)
594
+
595
+ is_afat = afat_active()
596
+ config = BigBrotherConfig.get_solo()
597
+ show_market = getattr(config, "show_market_transactions", False)
598
+
599
+ filtered = []
600
+ for app in app_list:
601
+ label = app.get("app_label")
602
+
603
+ # Exclude AFAT's own admin section if present.
604
+ if not is_afat and label == "afat":
605
+ continue
606
+
607
+ # Filter models within our app
608
+ if label == "aa_bb":
609
+ models = app.get("models", [])
610
+ if not is_afat:
611
+ models = [
612
+ m for m in models if m.get("object_name") not in _PAP_OBJECT_NAMES
613
+ ]
614
+ if not show_market:
615
+ models = [
616
+ m for m in models if m.get("object_name") not in _MARKET_OBJECT_NAMES
617
+ ]
618
+ app = {**app, "models": models}
619
+
620
+ # Drop empty app groups
621
+ if app.get("models"):
622
+ filtered.append(app)
623
+
624
+ return filtered
625
+
626
+
627
+ admin.site.get_app_list = _filtered_get_app_list