aa-bb 3.1.0b1__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 (179) hide show
  1. aa_bb/__init__.py +4 -0
  2. aa_bb/admin.py +484 -0
  3. aa_bb/app_settings.py +1063 -0
  4. aa_bb/app_settings_2.py +319 -0
  5. aa_bb/apps.py +481 -0
  6. aa_bb/auth_hooks.py +209 -0
  7. aa_bb/celery.py +55 -0
  8. aa_bb/checks/__init__.py +8 -0
  9. aa_bb/checks/alliance_blacklist.py +19 -0
  10. aa_bb/checks/alpha_skills.json +757 -0
  11. aa_bb/checks/awox.py +293 -0
  12. aa_bb/checks/clone_state.py +182 -0
  13. aa_bb/checks/coalition_blacklist.py +42 -0
  14. aa_bb/checks/corp_blacklist.py +190 -0
  15. aa_bb/checks/corp_changes.py +258 -0
  16. aa_bb/checks/cyno.py +377 -0
  17. aa_bb/checks/hostile_assets.py +443 -0
  18. aa_bb/checks/hostile_clones.py +501 -0
  19. aa_bb/checks/roles_and_tokens.py +209 -0
  20. aa_bb/checks/skills.json +659 -0
  21. aa_bb/checks/skills.py +289 -0
  22. aa_bb/checks/sus_contacts.py +288 -0
  23. aa_bb/checks/sus_contracts.py +259 -0
  24. aa_bb/checks/sus_mails.py +338 -0
  25. aa_bb/checks/sus_trans.py +345 -0
  26. aa_bb/checks_cb/__init__.py +7 -0
  27. aa_bb/checks_cb/hostile_assets.py +160 -0
  28. aa_bb/checks_cb/sus_contracts.py +271 -0
  29. aa_bb/checks_cb/sus_trans.py +296 -0
  30. aa_bb/esi_cache.py +39 -0
  31. aa_bb/esi_client.py +75 -0
  32. aa_bb/forms.py +52 -0
  33. aa_bb/locale/.gitkeep +0 -0
  34. aa_bb/management/__init__.py +0 -0
  35. aa_bb/management/commands/__init__.py +0 -0
  36. aa_bb/management/commands/manual_notif_test.py +655 -0
  37. aa_bb/migrations/0001_initial.py +25 -0
  38. aa_bb/migrations/0002_bigbrotherconfig_userstatus.py +58 -0
  39. aa_bb/migrations/0003_alter_bigbrotherconfig_pingroleid.py +18 -0
  40. aa_bb/migrations/0004_alter_bigbrotherconfig_is_active_and_more.py +23 -0
  41. aa_bb/migrations/0005_alter_bigbrotherconfig_hostile_alliances.py +18 -0
  42. aa_bb/migrations/0006_alter_bigbrotherconfig_pingroleid.py +18 -0
  43. aa_bb/migrations/0007_alter_general_options.py +17 -0
  44. aa_bb/migrations/0008_alliance_names_corporation_names.py +41 -0
  45. aa_bb/migrations/0009_userstatus_sus_contacts_userstatus_sus_contracts_and_more.py +38 -0
  46. aa_bb/migrations/0010_alter_userstatus_awox_kill_links_and_more.py +48 -0
  47. aa_bb/migrations/0011_character_names.py +27 -0
  48. aa_bb/migrations/0012_id_types.py +27 -0
  49. aa_bb/migrations/0013_bigbrotherconfig_mail_keywords.py +18 -0
  50. aa_bb/migrations/0014_processedcontract_processedmail_and_more.py +74 -0
  51. aa_bb/migrations/0015_processedtransaction_sustransactionnote.py +42 -0
  52. aa_bb/migrations/0016_warmprogress.py +23 -0
  53. aa_bb/migrations/0017_entityinfocache.py +27 -0
  54. aa_bb/migrations/0018_userstatus_cyno_userstatus_has_skills_and_more.py +28 -0
  55. aa_bb/migrations/0019_bigbrotherconfig_whitelist_alliances_and_more.py +23 -0
  56. aa_bb/migrations/0020_messages_bigbrotherconfig_are_daily_messages_active_and_more.py +35 -0
  57. aa_bb/migrations/0021_alter_messages_id_alter_messages_text.py +23 -0
  58. aa_bb/migrations/0022_messages_sent_in_cycle.py +18 -0
  59. aa_bb/migrations/0023_optmessages1_optmessages2_optmessages3_optmessages4_and_more.py +103 -0
  60. aa_bb/migrations/0024_bigbrotherconfig_dailyschedule_and_more.py +45 -0
  61. aa_bb/migrations/0025_alter_messages_options_alter_optmessages1_options_and_more.py +37 -0
  62. aa_bb/migrations/0026_alter_general_options_alter_bigbrotherconfig_id_and_more.py +97 -0
  63. aa_bb/migrations/0027_alter_general_options_bigbrotherconfig_is_loa_active_and_more.py +27 -0
  64. aa_bb/migrations/0028_alter_bigbrotherconfig_is_loa_active.py +18 -0
  65. aa_bb/migrations/0029_leaverequest_main_character.py +18 -0
  66. aa_bb/migrations/0030_alter_general_options.py +17 -0
  67. aa_bb/migrations/0031_bigbrotherconfig_loa_max_logoff_days_and_more.py +88 -0
  68. aa_bb/migrations/0032_alter_leaverequest_status.py +18 -0
  69. aa_bb/migrations/0033_messagetype_bigbrotherconfig_pingroleid2_and_more.py +45 -0
  70. aa_bb/migrations/0034_rename_last_updated_userstatus_updated.py +18 -0
  71. aa_bb/migrations/0035_alter_userstatus_options.py +17 -0
  72. aa_bb/migrations/0036_alter_general_options.py +17 -0
  73. aa_bb/migrations/0037_corpstatus.py +32 -0
  74. aa_bb/migrations/0038_bigbrotherconfig_ignored_corporations.py +18 -0
  75. aa_bb/migrations/0039_alter_bigbrotherconfig_ignored_corporations.py +18 -0
  76. aa_bb/migrations/0040_sovereigntymapcache_corporationinfocache_and_more.py +49 -0
  77. aa_bb/migrations/0041_bigbrotherconfig_is_warmer_active.py +18 -0
  78. aa_bb/migrations/0042_alter_general_options_and_more.py +23 -0
  79. aa_bb/migrations/0043_bigbrotherconfig_bb_member_states_and_more.py +24 -0
  80. aa_bb/migrations/0044_bigbrotherconfig_character_scopes_and_more.py +23 -0
  81. aa_bb/migrations/0045_userstatus_sp_age_ratio_result.py +18 -0
  82. aa_bb/migrations/0046_bigbrotherconfig_member_corporations.py +18 -0
  83. aa_bb/migrations/0047_bigbrotherconfig_member_alliances.py +18 -0
  84. aa_bb/migrations/0048_characteraccountstate.py +21 -0
  85. aa_bb/migrations/0049_userstatus_clone_status.py +18 -0
  86. aa_bb/migrations/0050_alter_general_options_and_more.py +22 -0
  87. aa_bb/migrations/0051_monthlypapstats.py +34 -0
  88. aa_bb/migrations/0052_papsconfig_delete_monthlypapstats.py +30 -0
  89. aa_bb/migrations/0053_alter_papsconfig_corp_modifier_and_more.py +38 -0
  90. aa_bb/migrations/0054_alter_general_options.py +17 -0
  91. aa_bb/migrations/0055_papsconfig_group_paps_papsconfig_group_paps_modifier.py +24 -0
  92. aa_bb/migrations/0056_alter_papsconfig_group_paps.py +19 -0
  93. aa_bb/migrations/0057_papsconfig_excluded_groups_papsconfig_excluded_users_and_more.py +31 -0
  94. aa_bb/migrations/0058_papsconfig_excluded_groups_get_paps_and_more.py +30 -0
  95. aa_bb/migrations/0059_alter_papsconfig_excluded_groups_get_paps.py +18 -0
  96. aa_bb/migrations/0060_papsconfig_cap_group_papsconfig_cap_group_paps_and_more.py +49 -0
  97. aa_bb/migrations/0061_remove_papsconfig_cap_group_and_more.py +42 -0
  98. aa_bb/migrations/0062_complianceticket.py +28 -0
  99. aa_bb/migrations/0063_alter_complianceticket_user.py +21 -0
  100. aa_bb/migrations/0064_alter_complianceticket_reason.py +18 -0
  101. aa_bb/migrations/0065_alter_complianceticket_reason_and_more.py +26 -0
  102. aa_bb/migrations/0066_tickettoolconfig_papcompliance.py +53 -0
  103. aa_bb/migrations/0067_remove_tickettoolconfig_category_id_and_more.py +21 -0
  104. aa_bb/migrations/0068_tickettoolconfig_category_id_and_more.py +23 -0
  105. aa_bb/migrations/0069_tickettoolconfig_ticket_counter.py +18 -0
  106. aa_bb/migrations/0070_remove_tickettoolconfig_role_id_and_more.py +54 -0
  107. aa_bb/migrations/0071_tickettoolconfig_role_id.py +18 -0
  108. aa_bb/migrations/0072_alter_complianceticket_reason.py +18 -0
  109. aa_bb/migrations/0073_tickettoolconfig_starting_pap_compliance.py +18 -0
  110. aa_bb/migrations/0074_alter_bigbrotherconfig_hostile_alliances.py +18 -0
  111. aa_bb/migrations/0075_bbupdatestate_tickettoolconfig_afk_check_frequency_and_more.py +49 -0
  112. aa_bb/migrations/0076_id_types_last_accessed_frequentcorpchangescache_and_more.py +74 -0
  113. aa_bb/migrations/0077_complianceticket_ticket_id_and_more.py +28 -0
  114. aa_bb/migrations/0078_tickettoolconfig_awox_monitor_enabled.py +18 -0
  115. aa_bb/migrations/0079_helptext_guidance.py +57 -0
  116. aa_bb/migrations/0080_bigbrotherconfig_dlc_flags.py +65 -0
  117. aa_bb/migrations/0081_remove_papsconfig_imp_modifier_and_more.py +56 -0
  118. aa_bb/migrations/0082_remove_bigbrotherconfig_token_and_more.py +82 -0
  119. aa_bb/migrations/0083_alter_bigbrotherredditmessage_options_and_more.py +71 -0
  120. aa_bb/migrations/0084_bigbrotherconfig_consider_all_structures_hostile_and_more.py +43 -0
  121. aa_bb/migrations/0085_bigbrotherconfig_awox_notify_and_more.py +33 -0
  122. aa_bb/migrations/0086_bigbrotherconfig_are_recurring_stats_active_and_more.py +54 -0
  123. aa_bb/migrations/0087_bigbrotherconfig_dlc_are_recurring_stats_active.py +18 -0
  124. aa_bb/migrations/0088_bigbrotherconfig_cyno_notify.py +18 -0
  125. aa_bb/migrations/0089_bigbrotherconfig_asset_notify_and_more.py +58 -0
  126. aa_bb/migrations/__init__.py +0 -0
  127. aa_bb/models.py +1289 -0
  128. aa_bb/modelss.py +625 -0
  129. aa_bb/reddit.py +134 -0
  130. aa_bb/signals.py +66 -0
  131. aa_bb/static/aa_bb/.gitkeep +0 -0
  132. aa_bb/tasks.py +1267 -0
  133. aa_bb/tasks_bot.py +287 -0
  134. aa_bb/tasks_cb.py +1096 -0
  135. aa_bb/tasks_ct.py +348 -0
  136. aa_bb/tasks_other.py +237 -0
  137. aa_bb/tasks_reddit.py +180 -0
  138. aa_bb/tasks_tickets.py +425 -0
  139. aa_bb/templates/aa_bb/base.html +25 -0
  140. aa_bb/templates/aa_bb/disabled.html +20 -0
  141. aa_bb/templates/aa_bb/index.html +554 -0
  142. aa_bb/templates/aa_cb/base.html +26 -0
  143. aa_bb/templates/aa_cb/disabled.html +20 -0
  144. aa_bb/templates/aa_cb/index.html +398 -0
  145. aa_bb/templates/faq/base.html +19 -0
  146. aa_bb/templates/faq/cards.html +116 -0
  147. aa_bb/templates/faq/faq.html +42 -0
  148. aa_bb/templates/faq/menu.html +30 -0
  149. aa_bb/templates/faq/modules.html +66 -0
  150. aa_bb/templates/faq/settings_bigbrother.html +224 -0
  151. aa_bb/templates/faq/settings_nav.html +23 -0
  152. aa_bb/templates/faq/settings_paps.html +132 -0
  153. aa_bb/templates/faq/settings_tickets.html +226 -0
  154. aa_bb/templates/loa/_loa_subtabs.html +17 -0
  155. aa_bb/templates/loa/admin.html +112 -0
  156. aa_bb/templates/loa/base.html +22 -0
  157. aa_bb/templates/loa/disabled.html +19 -0
  158. aa_bb/templates/loa/index.html +62 -0
  159. aa_bb/templates/loa/menu.html +27 -0
  160. aa_bb/templates/loa/request.html +81 -0
  161. aa_bb/templates/paps/base.html +21 -0
  162. aa_bb/templates/paps/disabled.html +19 -0
  163. aa_bb/templates/paps/history.html +25 -0
  164. aa_bb/templates/paps/index.html +86 -0
  165. aa_bb/templates/paps/menu.html +20 -0
  166. aa_bb/tests/__init__.py +1 -0
  167. aa_bb/tests/test_example.py +31 -0
  168. aa_bb/urls.py +57 -0
  169. aa_bb/urls_cb.py +27 -0
  170. aa_bb/urls_loa.py +14 -0
  171. aa_bb/urls_paps.py +10 -0
  172. aa_bb/views.py +1113 -0
  173. aa_bb/views_cb.py +706 -0
  174. aa_bb/views_faq.py +918 -0
  175. aa_bb/views_paps.py +334 -0
  176. aa_bb-3.1.0b1.dist-info/METADATA +356 -0
  177. aa_bb-3.1.0b1.dist-info/RECORD +179 -0
  178. aa_bb-3.1.0b1.dist-info/WHEEL +4 -0
  179. aa_bb-3.1.0b1.dist-info/licenses/LICENSE +7 -0
aa_bb/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ """Initialize the app"""
2
+
3
+ __version__ = "3.1.0b1"
4
+ __title__ = "BigBrother"
aa_bb/admin.py ADDED
@@ -0,0 +1,484 @@
1
+ """
2
+ Admin registrations for every BigBrother-related model.
3
+
4
+ Most models are singletons that gate optional DLC 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
+
13
+ from .models import (
14
+ BigBrotherConfig,
15
+ Messages,
16
+ OptMessages1,
17
+ OptMessages2,
18
+ OptMessages3,
19
+ OptMessages4,
20
+ OptMessages5,
21
+ UserStatus,
22
+ WarmProgress,
23
+ PapsConfig,
24
+ RecurringStatsConfig,
25
+ )
26
+ from .modelss import (
27
+ TicketToolConfig,
28
+ PapCompliance,
29
+ LeaveRequest,
30
+ ComplianceTicket,
31
+ BigBrotherRedditSettings,
32
+ BigBrotherRedditMessage,
33
+ )
34
+ from .reddit import is_reddit_module_visible
35
+ from django.core.exceptions import ObjectDoesNotExist
36
+
37
+
38
+ class DLCVisibilityMixin:
39
+ """Hide admin entries when the related DLC flag is disabled."""
40
+
41
+ dlc_attr = None
42
+
43
+ def _allowed(self) -> bool:
44
+ """Return True when the DLC attribute is enabled or not required."""
45
+ if not self.dlc_attr: # Always allow when no DLC flag is configured.
46
+ return True
47
+ try:
48
+ cfg = BigBrotherConfig.get_solo()
49
+ except ObjectDoesNotExist:
50
+ return False
51
+ return bool(getattr(cfg, self.dlc_attr, False))
52
+
53
+ def has_module_permission(self, request):
54
+ """Hide the entire admin module when the DLC is disabled."""
55
+ return self._allowed() and super().has_module_permission(request)
56
+
57
+ def has_view_permission(self, request, obj=None):
58
+ """Disable read access when the DLC is disabled."""
59
+ return self._allowed() and super().has_view_permission(request, obj)
60
+
61
+ def has_add_permission(self, request):
62
+ """Disable add operations when the DLC is disabled."""
63
+ return self._allowed() and super().has_add_permission(request)
64
+
65
+ def has_change_permission(self, request, obj=None):
66
+ """Disable edit operations when the DLC is disabled."""
67
+ return self._allowed() and super().has_change_permission(request, obj)
68
+
69
+ def has_delete_permission(self, request, obj=None):
70
+ """Disable delete operations when the DLC is disabled."""
71
+ return self._allowed() and super().has_delete_permission(request, obj)
72
+
73
+
74
+ class PapModuleVisibilityMixin(DLCVisibilityMixin):
75
+ """Restrict admin entries to installs with the PAP DLC."""
76
+ dlc_attr = "dlc_pap_active"
77
+
78
+
79
+ class TicketModuleVisibilityMixin(DLCVisibilityMixin):
80
+ """Restrict admin entries to installs with the ticketing DLC."""
81
+ dlc_attr = "dlc_tickets_active"
82
+
83
+
84
+ class LoaModuleVisibilityMixin(DLCVisibilityMixin):
85
+ """Show admin pages only when the LoA module is enabled."""
86
+ dlc_attr = "dlc_loa_active"
87
+
88
+
89
+ class DailyMessagesVisibilityMixin(DLCVisibilityMixin):
90
+ """Hide daily/optional message models when that DLC is off."""
91
+ dlc_attr = "dlc_daily_messages_active"
92
+
93
+
94
+ class RecurringStatsVisibilityMixin(DLCVisibilityMixin):
95
+ """Hide recurring stats models when that DLC is off."""
96
+ dlc_attr = "dlc_are_recurring_stats_active"
97
+
98
+
99
+ @admin.register(BigBrotherConfig)
100
+ class BB_ConfigAdmin(SingletonModelAdmin):
101
+ fieldsets = (
102
+ (
103
+ "Core Activation",
104
+ {
105
+ "fields": (
106
+ "is_active",
107
+ "is_warmer_active",
108
+ "is_loa_active",
109
+ "is_paps_active",
110
+ "are_daily_messages_active",
111
+ "are_recurring_stats_active",
112
+ "are_opt_messages1_active",
113
+ "are_opt_messages2_active",
114
+ "are_opt_messages3_active",
115
+ "are_opt_messages4_active",
116
+ "are_opt_messages5_active",
117
+ "loa_max_logoff_days",
118
+ )
119
+ },
120
+ ),
121
+ (
122
+ "DLC Flags",
123
+ {
124
+ "classes": ("collapse",),
125
+ "fields": (
126
+ "dlc_corp_brother_active",
127
+ "dlc_loa_active",
128
+ "dlc_pap_active",
129
+ "dlc_tickets_active",
130
+ "dlc_reddit_active",
131
+ "dlc_daily_messages_active",
132
+ "dlc_are_recurring_stats_active",
133
+ ),
134
+ },
135
+ ),
136
+ (
137
+ "Notifications",
138
+ {
139
+ "fields": (
140
+ "ct_notify",
141
+ "awox_notify",
142
+ "cyno_notify",
143
+ "sp_inject_notify",
144
+ "clone_notify",
145
+ "asset_notify",
146
+ "contact_notify",
147
+ "contract_notify",
148
+ "mail_notify",
149
+ "transaction_notify",
150
+ "new_user_notify",
151
+ ),
152
+ },
153
+ ),
154
+ (
155
+ "Ping / Messaging Roles",
156
+ {
157
+ "fields": (
158
+ "pingroleID",
159
+ "pingroleID2",
160
+ "pingrole1_messages",
161
+ "pingrole2_messages",
162
+ "here_messages",
163
+ "everyone_messages",
164
+ )
165
+ },
166
+ ),
167
+ (
168
+ "Webhooks",
169
+ {
170
+ "fields": (
171
+ "webhook",
172
+ "loawebhook",
173
+ "dailywebhook",
174
+ "optwebhook1",
175
+ "optwebhook2",
176
+ "optwebhook3",
177
+ "optwebhook4",
178
+ "optwebhook5",
179
+ "stats_webhook",
180
+ )
181
+ },
182
+ ),
183
+ (
184
+ "Schedules",
185
+ {
186
+ "fields": (
187
+ "dailyschedule",
188
+ "optschedule1",
189
+ "optschedule2",
190
+ "optschedule3",
191
+ "optschedule4",
192
+ "optschedule5",
193
+ "stats_schedule",
194
+ ),
195
+ },
196
+ ),
197
+ (
198
+ "User State & Membership",
199
+ {
200
+ "fields": (
201
+ "bb_guest_states",
202
+ "bb_member_states",
203
+ "member_corporations",
204
+ "member_alliances",
205
+ "ignored_corporations",
206
+ )
207
+ },
208
+ ),
209
+ (
210
+ "Hostile / Whitelist Rules",
211
+ {
212
+ "fields": (
213
+ "hostile_alliances",
214
+ "hostile_corporations",
215
+ "whitelist_alliances",
216
+ "whitelist_corporations",
217
+ "consider_nullsec_hostile",
218
+ "consider_all_structures_hostile",
219
+ "consider_npc_stations_hostile",
220
+ "excluded_systems",
221
+ "excluded_stations",
222
+ "hostile_assets_ships_only",
223
+ )
224
+ },
225
+ ),
226
+ (
227
+ "Scopes",
228
+ {
229
+ "classes": ("collapse",),
230
+ "fields": (
231
+ "character_scopes",
232
+ "corporation_scopes",
233
+ ),
234
+ },
235
+ ),
236
+ (
237
+ "Main Corp / Alliance",
238
+ {
239
+ "fields": (
240
+ "main_corporation_id",
241
+ "main_corporation",
242
+ "main_alliance_id",
243
+ "main_alliance",
244
+ ),
245
+ },
246
+ ),
247
+ )
248
+ """Singleton config for the core BigBrother module."""
249
+ readonly_fields = (
250
+ "main_corporation",
251
+ "main_alliance",
252
+ "main_corporation_id",
253
+ "main_alliance_id",
254
+ "is_active",
255
+ "dlc_corp_brother_active",
256
+ "dlc_loa_active",
257
+ "dlc_pap_active",
258
+ "dlc_tickets_active",
259
+ "dlc_reddit_active",
260
+ "dlc_daily_messages_active",
261
+ "dlc_are_recurring_stats_active",
262
+ )
263
+ filter_horizontal = (
264
+ "pingrole1_messages",
265
+ "pingrole2_messages",
266
+ "here_messages",
267
+ "everyone_messages",
268
+ "bb_guest_states",
269
+ "bb_member_states",
270
+ )
271
+
272
+ def has_add_permission(self, request):
273
+ """Prevent duplicate singleton rows."""
274
+ if BigBrotherConfig.objects.exists(): # Disallow when a config already exists.
275
+ return False
276
+ return super().has_add_permission(request)
277
+
278
+ def has_delete_permission(self, request, obj=None):
279
+ """Always allow delete to keep parity with default behavior."""
280
+ return True
281
+
282
+
283
+ @admin.register(PapsConfig)
284
+ class PapsConfigAdmin(PapModuleVisibilityMixin, SingletonModelAdmin):
285
+ """Controls PAP multipliers/thresholds; singleton per install."""
286
+ filter_horizontal = (
287
+ "group_paps",
288
+ "excluded_groups",
289
+ "excluded_users",
290
+ "excluded_users_paps",
291
+ )
292
+
293
+ def has_add_permission(self, request):
294
+ """Prevent duplicate PAP config entries."""
295
+ if PapsConfig.objects.exists(): # Disallow singleton duplication.
296
+ return False
297
+ return super().has_add_permission(request)
298
+
299
+ def has_delete_permission(self, request, obj=None):
300
+ """Allow deletes so admins can rebuild the configuration."""
301
+ return True
302
+
303
+
304
+ @admin.register(TicketToolConfig)
305
+ class TicketToolConfigAdmin(TicketModuleVisibilityMixin, SingletonModelAdmin):
306
+ """Ticket automation thresholds + templates."""
307
+ filter_horizontal = (
308
+ "excluded_users",
309
+ )
310
+
311
+ def has_add_permission(self, request):
312
+ """Prevent duplicate ticket config entries."""
313
+ if PapsConfig.objects.exists(): # Ticket config should remain singleton.
314
+ return False
315
+ return super().has_add_permission(request)
316
+
317
+ def has_delete_permission(self, request, obj=None):
318
+ """Allow deletes when operators need to reset settings."""
319
+ return True
320
+
321
+
322
+ class RedditAdminVisibilityMixin(DLCVisibilityMixin):
323
+ """Hide Reddit admin entries unless DLC + feature flag are active."""
324
+ dlc_attr = "dlc_reddit_active"
325
+
326
+ def _allowed(self) -> bool:
327
+ """Require both DLC activation and reddit module visibility."""
328
+ return super()._allowed() and is_reddit_module_visible()
329
+
330
+
331
+ @admin.register(BigBrotherRedditSettings)
332
+ class BigBrotherRedditSettingsAdmin(RedditAdminVisibilityMixin, SingletonModelAdmin):
333
+ """OAuth tokens + scheduling info for the Reddit autoposter."""
334
+ exclude = (
335
+ "reddit_access_token",
336
+ "reddit_refresh_token",
337
+ "reddit_token_type",
338
+ "last_submission_id",
339
+ "last_submission_permalink",
340
+ "reddit_account_name",
341
+ )
342
+ readonly_fields = (
343
+ "reddit_token_obtained",
344
+ "last_submission_at",
345
+ "last_reply_checked_at",
346
+ "reddit_account_name",
347
+ )
348
+
349
+ def has_add_permission(self, request):
350
+ """Limit the settings model to a single row."""
351
+ if BigBrotherRedditSettings.objects.exists(): # Disallow duplicate settings.
352
+ return False
353
+ return super().has_add_permission(request)
354
+
355
+ def has_delete_permission(self, request, obj=None):
356
+ """Never allow deleting the Reddit credentials row."""
357
+ return False
358
+
359
+
360
+ @admin.register(BigBrotherRedditMessage)
361
+ class BigBrotherRedditMessageAdmin(RedditAdminVisibilityMixin, admin.ModelAdmin):
362
+ """Manage the pool of canned Reddit ads."""
363
+ list_display = ("title", "used_in_cycle", "created")
364
+ list_filter = ("used_in_cycle",)
365
+ search_fields = ("title", "content")
366
+
367
+
368
+ @admin.register(Messages)
369
+ class DailyMessageConfig(DailyMessagesVisibilityMixin, admin.ModelAdmin):
370
+ """Standard daily webhook messages rotated each cycle."""
371
+ search_fields = ["text"]
372
+ list_display = ["text", "sent_in_cycle"]
373
+
374
+
375
+ @admin.register(OptMessages1)
376
+ class OptMessage1Config(DailyMessagesVisibilityMixin, admin.ModelAdmin):
377
+ """Optional webhook stream #1."""
378
+ search_fields = ["text"]
379
+ list_display = ["text", "sent_in_cycle"]
380
+
381
+
382
+ @admin.register(OptMessages2)
383
+ class OptMessage2Config(DailyMessagesVisibilityMixin, admin.ModelAdmin):
384
+ """Optional webhook stream #2."""
385
+ search_fields = ["text"]
386
+ list_display = ["text", "sent_in_cycle"]
387
+
388
+
389
+ @admin.register(OptMessages3)
390
+ class OptMessage3Config(DailyMessagesVisibilityMixin, admin.ModelAdmin):
391
+ """Optional webhook stream #3."""
392
+ search_fields = ["text"]
393
+ list_display = ["text", "sent_in_cycle"]
394
+
395
+
396
+ @admin.register(OptMessages4)
397
+ class OptMessage4Config(DailyMessagesVisibilityMixin, admin.ModelAdmin):
398
+ """Optional webhook stream #4."""
399
+ search_fields = ["text"]
400
+ list_display = ["text", "sent_in_cycle"]
401
+
402
+
403
+ @admin.register(OptMessages5)
404
+ class OptMessage5Config(DailyMessagesVisibilityMixin, admin.ModelAdmin):
405
+ """Optional webhook stream #5."""
406
+ search_fields = ["text"]
407
+ list_display = ["text", "sent_in_cycle"]
408
+
409
+
410
+ @admin.register(WarmProgress)
411
+ class WarmProgressConfig(admin.ModelAdmin):
412
+ """Shows which users the cache warmer has processed recently."""
413
+ list_display = ["user_main", "updated"]
414
+
415
+
416
+ @admin.register(UserStatus)
417
+ class UserStatusConfig(admin.ModelAdmin):
418
+ """Simple heartbeat for per-user card status."""
419
+ list_display = ["user", "updated"]
420
+
421
+
422
+ @admin.register(ComplianceTicket)
423
+ class ComplianceTicketConfig(TicketModuleVisibilityMixin, admin.ModelAdmin):
424
+ """History of tickets issued by the automation layer."""
425
+ list_display = ["user", "ticket_id", "reason"]
426
+
427
+
428
+ @admin.register(LeaveRequest)
429
+ class LeaveRequestConfig(LoaModuleVisibilityMixin, admin.ModelAdmin):
430
+ """Expose LeaveRequest records to staff when LoA is enabled."""
431
+ list_display = ["main_character", "start_date", "end_date", "reason", "status"]
432
+
433
+
434
+ @admin.register(PapCompliance)
435
+ class PapComplianceConfig(PapModuleVisibilityMixin, admin.ModelAdmin):
436
+ """Shows the most recent PAP compliance calculation per user."""
437
+ search_fields = ["user_profile"]
438
+ list_display = ["user_profile", "pap_compliant"]
439
+
440
+
441
+ @admin.register(RecurringStatsConfig)
442
+ class RecurringStatsConfigAdmin(SingletonModelAdmin):
443
+ fieldsets = (
444
+ (
445
+ "General",
446
+ {
447
+ "fields": ("enabled",),
448
+ },
449
+ ),
450
+ (
451
+ "States",
452
+ {
453
+ "fields": ("states",),
454
+ "description": "Select which states you want broken out (Member, Blue, Alumni, etc.).",
455
+ },
456
+ ),
457
+ (
458
+ "Included Stats",
459
+ {
460
+ "fields": (
461
+ "include_auth_users",
462
+ "include_discord_users",
463
+ "include_mumble_users",
464
+ "include_characters",
465
+ "include_corporations",
466
+ "include_alliances",
467
+ "include_tokens",
468
+ "include_unique_tokens",
469
+ "include_character_audits",
470
+ "include_corporation_audits",
471
+ ),
472
+ },
473
+ ),
474
+ (
475
+ "Internal",
476
+ {
477
+ "fields": ("last_run_at", "last_snapshot"),
478
+ "classes": ("collapse",),
479
+ },
480
+ ),
481
+ )
482
+
483
+ filter_horizontal = ("states",)
484
+ readonly_fields = ("last_run_at", "last_snapshot")