aa-ledger 0.9.8__py3-none-any.whl → 0.9.9.1__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.
- {aa_ledger-0.9.8.dist-info → aa_ledger-0.9.9.1.dist-info}/METADATA +3 -1
- {aa_ledger-0.9.8.dist-info → aa_ledger-0.9.9.1.dist-info}/RECORD +78 -76
- ledger/__init__.py +9 -9
- ledger/api/api_helper/billboard_helper.py +55 -26
- ledger/api/ledger/admin.py +1 -1
- ledger/app_settings.py +18 -11
- ledger/constants.py +5 -0
- ledger/decorators.py +92 -11
- ledger/helpers/alliance.py +119 -91
- ledger/helpers/character.py +260 -252
- ledger/helpers/core.py +565 -565
- ledger/helpers/corporation.py +237 -187
- ledger/helpers/etag.py +2 -1
- ledger/helpers/ref_type.py +475 -475
- ledger/locale/cs_CZ/LC_MESSAGES/django.po +942 -932
- ledger/locale/de/LC_MESSAGES/django.mo +0 -0
- ledger/locale/de/LC_MESSAGES/django.po +961 -945
- ledger/locale/django.pot +942 -932
- ledger/locale/es/LC_MESSAGES/django.po +943 -933
- ledger/locale/fr_FR/LC_MESSAGES/django.po +942 -932
- ledger/locale/it_IT/LC_MESSAGES/django.po +942 -932
- ledger/locale/ja/LC_MESSAGES/django.po +943 -933
- ledger/locale/ko_KR/LC_MESSAGES/django.po +942 -932
- ledger/locale/nl_NL/LC_MESSAGES/django.po +942 -932
- ledger/locale/pl_PL/LC_MESSAGES/django.po +942 -932
- ledger/locale/ru/LC_MESSAGES/django.po +945 -935
- ledger/locale/sk/LC_MESSAGES/django.po +944 -934
- ledger/locale/uk/LC_MESSAGES/django.po +946 -936
- ledger/locale/zh_Hans/LC_MESSAGES/django.po +943 -933
- ledger/managers/character_mining_manager.py +66 -19
- ledger/managers/character_planetary_manager.py +1 -1
- ledger/migrations/0016_characterminingledger_price_per_unit.py +21 -0
- ledger/models/characteraudit.py +32 -1
- ledger/static/ledger/css/cards.css +1 -1
- ledger/static/ledger/css/table.css +1 -1
- ledger/static/ledger/js/charts.js +7 -227
- ledger/static/ledger/js/planetary.js +1 -0
- ledger/tasks.py +1 -8
- ledger/templates/ledger/allyledger/admin/alliance_administration.html +17 -8
- ledger/templates/ledger/allyledger/admin/alliance_overview.html +75 -89
- ledger/templates/ledger/allyledger/alliance_ledger.html +8 -10
- ledger/templates/ledger/bundles/ally-administration-bundles.html +2 -0
- ledger/templates/ledger/bundles/char-administration-bundles.html +2 -0
- ledger/templates/ledger/bundles/character-ledger-bundles.html +66 -64
- ledger/templates/ledger/bundles/corp-administration-bundles.html +2 -0
- ledger/templates/ledger/bundles/corporation-ledger-bundles.html +75 -73
- ledger/templates/ledger/charledger/admin/character_administration.html +10 -8
- ledger/templates/ledger/charledger/admin/character_overview.html +69 -86
- ledger/templates/ledger/charledger/character_ledger.html +11 -15
- ledger/templates/ledger/charledger/planetary/planetary_ledger.html +2 -6
- ledger/templates/ledger/corpledger/admin/corporation_administration.html +10 -8
- ledger/templates/ledger/corpledger/admin/corporation_overview.html +71 -83
- ledger/templates/ledger/corpledger/corporation_ledger.html +55 -14
- ledger/templates/ledger/partials/administration/alliance.html +28 -49
- ledger/templates/ledger/partials/administration/alliance_corporations.html +58 -0
- ledger/templates/ledger/partials/administration/corporation_characters.html +26 -28
- ledger/templates/ledger/partials/information/daily.html +1 -1
- ledger/templates/ledger/partials/information/day.html +1 -7
- ledger/templates/ledger/partials/information/hourly.html +1 -7
- ledger/templates/ledger/partials/information/summary.html +88 -84
- ledger/templates/ledger/partials/information/view_character_content.html +35 -35
- ledger/templates/ledger/partials/table/char-ledger.html +14 -5
- ledger/templates/ledger/partials/table/corp-ledger.html +3 -3
- ledger/templates/ledger/partials/view/card.html +2 -2
- ledger/tests/test_decarators.py +102 -17
- ledger/tests/test_helpers/test_etag.py +7 -6
- ledger/tests/test_managers/test_character_mining_manager.py +2 -1
- ledger/tests/test_models/test_characterminingledger.py +38 -2
- ledger/tests/test_tasks.py +4 -4
- ledger/tests/test_templatetags.py +5 -2
- ledger/tests/test_views/test_access.py +852 -852
- ledger/tests/testdata/esi.json +1 -2
- ledger/tests/testdata/eveuniverse.json +90 -48
- ledger/urls.py +66 -21
- ledger/views/alliance/alliance_ledger.py +4 -3
- ledger/views/corporation/corporation_ledger.py +25 -9
- {aa_ledger-0.9.8.dist-info → aa_ledger-0.9.9.1.dist-info}/WHEEL +0 -0
- {aa_ledger-0.9.8.dist-info → aa_ledger-0.9.9.1.dist-info}/licenses/LICENSE +0 -0
ledger/helpers/core.py
CHANGED
|
@@ -1,565 +1,565 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Core View Helper
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
# Standard Library
|
|
6
|
-
from decimal import Decimal
|
|
7
|
-
from hashlib import md5
|
|
8
|
-
|
|
9
|
-
# Django
|
|
10
|
-
from django.
|
|
11
|
-
from django.
|
|
12
|
-
from django.
|
|
13
|
-
from django.utils import
|
|
14
|
-
from django.utils.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
from allianceauth.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
from ledger import
|
|
31
|
-
from ledger.
|
|
32
|
-
from ledger.
|
|
33
|
-
from ledger.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
#
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
class
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
self.
|
|
65
|
-
self.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
self.
|
|
82
|
-
self.
|
|
83
|
-
self.
|
|
84
|
-
self.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
self.
|
|
88
|
-
self.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
self.
|
|
92
|
-
self.
|
|
93
|
-
self.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
self.
|
|
97
|
-
self.
|
|
98
|
-
self.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
self.
|
|
104
|
-
self.
|
|
105
|
-
self.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
self.
|
|
109
|
-
self.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
character
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
button_html
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
self.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
self.
|
|
198
|
-
self.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
""
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
"
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
if self.year and self.month
|
|
351
|
-
return reverse(
|
|
352
|
-
f"ledger:{viewname}
|
|
353
|
-
kwargs={
|
|
354
|
-
**kwargs,
|
|
355
|
-
"year": self.year,
|
|
356
|
-
"month": self.month,
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
"
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
entity:
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
"
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
amounts
|
|
497
|
-
return amounts
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
self.
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
)
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
1
|
+
"""
|
|
2
|
+
Core View Helper
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# Standard Library
|
|
6
|
+
from decimal import Decimal
|
|
7
|
+
from hashlib import md5
|
|
8
|
+
|
|
9
|
+
# Django
|
|
10
|
+
from django.db.models import Q, QuerySet, Sum
|
|
11
|
+
from django.urls import reverse
|
|
12
|
+
from django.utils import timezone
|
|
13
|
+
from django.utils.safestring import mark_safe
|
|
14
|
+
from django.utils.translation import gettext as _
|
|
15
|
+
|
|
16
|
+
# Alliance Auth
|
|
17
|
+
from allianceauth.authentication.models import CharacterOwnership, UserProfile
|
|
18
|
+
from allianceauth.eveonline.models import (
|
|
19
|
+
EveAllianceInfo,
|
|
20
|
+
EveCharacter,
|
|
21
|
+
EveCorporationInfo,
|
|
22
|
+
)
|
|
23
|
+
from allianceauth.services.hooks import get_extension_logger
|
|
24
|
+
|
|
25
|
+
# Alliance Auth (External Libs)
|
|
26
|
+
from app_utils.logging import LoggerAddTag
|
|
27
|
+
|
|
28
|
+
# AA Ledger
|
|
29
|
+
from ledger import __title__
|
|
30
|
+
from ledger.api.api_helper.billboard_helper import BillboardSystem
|
|
31
|
+
from ledger.app_settings import LEDGER_CACHE_KEY
|
|
32
|
+
from ledger.helpers.ref_type import RefTypeManager
|
|
33
|
+
from ledger.models.general import EveEntity
|
|
34
|
+
|
|
35
|
+
logger = LoggerAddTag(get_extension_logger(__name__), __title__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def add_info_to_context(request, context: dict) -> dict:
|
|
39
|
+
"""Add additional information to the context for the view."""
|
|
40
|
+
# pylint: disable=import-outside-toplevel
|
|
41
|
+
# AA Ledger
|
|
42
|
+
from ledger.models.characteraudit import CharacterAudit
|
|
43
|
+
|
|
44
|
+
total_issues = (
|
|
45
|
+
CharacterAudit.objects.annotate_total_update_status_user(user=request.user)
|
|
46
|
+
.aggregate(total_failed=Sum("num_sections_failed"))
|
|
47
|
+
.get("total_failed", 0)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
new_context = {
|
|
51
|
+
**{
|
|
52
|
+
"issues": total_issues,
|
|
53
|
+
},
|
|
54
|
+
**context,
|
|
55
|
+
}
|
|
56
|
+
return new_context
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class DummyEveEntity:
|
|
60
|
+
"""Dummy Eve Entity class for fallback when no entity is found."""
|
|
61
|
+
|
|
62
|
+
def __init__(self, entity_id, entity_name="Unknown"):
|
|
63
|
+
self.entity_id = entity_id
|
|
64
|
+
self.entity_name = entity_name
|
|
65
|
+
self.type = "character"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class LedgerEntity:
|
|
69
|
+
"""Class to hold character or corporation data for the ledger."""
|
|
70
|
+
|
|
71
|
+
# pylint: disable=too-many-positional-arguments
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
entity_id,
|
|
75
|
+
character_obj: EveCharacter = None,
|
|
76
|
+
corporation_obj: EveCorporationInfo = None,
|
|
77
|
+
alliance_obj: EveAllianceInfo = None,
|
|
78
|
+
details_url=None,
|
|
79
|
+
):
|
|
80
|
+
self.type = "character"
|
|
81
|
+
self.entity = None
|
|
82
|
+
self.entity_id = entity_id
|
|
83
|
+
self.entity_name = None
|
|
84
|
+
self.details_url = details_url
|
|
85
|
+
if character_obj and hasattr(character_obj, "character_id"):
|
|
86
|
+
self.entity = character_obj
|
|
87
|
+
self.entity_id = character_obj.character_id
|
|
88
|
+
self.entity_name = character_obj.character_name
|
|
89
|
+
elif corporation_obj and hasattr(corporation_obj, "corporation_id"):
|
|
90
|
+
self.entity = corporation_obj
|
|
91
|
+
self.entity_id = corporation_obj.corporation_id
|
|
92
|
+
self.entity_name = corporation_obj.corporation_name
|
|
93
|
+
self.type = "corporation"
|
|
94
|
+
elif alliance_obj and hasattr(alliance_obj, "alliance_id"):
|
|
95
|
+
self.entity = alliance_obj
|
|
96
|
+
self.entity_id = alliance_obj.alliance_id
|
|
97
|
+
self.entity_name = alliance_obj.alliance_name
|
|
98
|
+
self.type = "alliance"
|
|
99
|
+
else:
|
|
100
|
+
try:
|
|
101
|
+
entity_obj = EveEntity.objects.get(eve_id=entity_id)
|
|
102
|
+
self.entity = entity_obj
|
|
103
|
+
self.entity_id = entity_obj.eve_id
|
|
104
|
+
self.entity_name = entity_obj.name
|
|
105
|
+
self.type = entity_obj.category
|
|
106
|
+
except EveEntity.DoesNotExist:
|
|
107
|
+
self.entity = DummyEveEntity(entity_id, "Unknown")
|
|
108
|
+
self.entity_id = entity_id
|
|
109
|
+
self.entity_name = "Unknown"
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def is_eve_character(self):
|
|
113
|
+
"""Check if the entity is an Eve Character."""
|
|
114
|
+
return isinstance(self.entity, EveCharacter)
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def is_eve_corporation(self):
|
|
118
|
+
"""Check if the entity is an Eve Corporation."""
|
|
119
|
+
return isinstance(self.entity, EveCorporationInfo)
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def is_eve_entity(self):
|
|
123
|
+
"""Check if the entity is an Eve Entity."""
|
|
124
|
+
return isinstance(self.entity, EveEntity)
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def alts(self) -> QuerySet[EveCharacter]:
|
|
128
|
+
"""Get all alts for this character."""
|
|
129
|
+
if not isinstance(self.entity, EveCharacter):
|
|
130
|
+
raise ValueError("Entity is not an EveCharacter.")
|
|
131
|
+
alts = EveCharacter.objects.filter(
|
|
132
|
+
character_ownership__user=self.entity.character_ownership.user
|
|
133
|
+
).select_related(
|
|
134
|
+
"character_ownership",
|
|
135
|
+
)
|
|
136
|
+
return alts
|
|
137
|
+
|
|
138
|
+
def portrait_url(self):
|
|
139
|
+
"""Return the portrait URL for the entity."""
|
|
140
|
+
try:
|
|
141
|
+
if isinstance(self.entity, EveCorporationInfo):
|
|
142
|
+
return self.entity.logo_url_32
|
|
143
|
+
|
|
144
|
+
if hasattr(self.entity, "portrait_url"):
|
|
145
|
+
return self.entity.portrait_url(size=32)
|
|
146
|
+
|
|
147
|
+
if self.entity.category == "faction":
|
|
148
|
+
return ""
|
|
149
|
+
return self.entity.icon_url(size=32)
|
|
150
|
+
except Exception as e: # pylint: disable=broad-except
|
|
151
|
+
logger.error(f"Error getting portrait URL for {self.entity_id}: {e}")
|
|
152
|
+
return ""
|
|
153
|
+
|
|
154
|
+
def add_details_url(self, details_url):
|
|
155
|
+
"""Set the details URL for the entity."""
|
|
156
|
+
self.details_url = details_url
|
|
157
|
+
|
|
158
|
+
def get_alts_ids_or_self(self):
|
|
159
|
+
"""Return the IDs of all alternative characters or the character ID itself."""
|
|
160
|
+
try:
|
|
161
|
+
character = EveCharacter.objects.get(character_id=self.entity_id)
|
|
162
|
+
if hasattr(character, "character_ownership"):
|
|
163
|
+
alt_ids = character.character_ownership.user.character_ownerships.all().values_list(
|
|
164
|
+
"character__character_id", flat=True
|
|
165
|
+
)
|
|
166
|
+
return list(alt_ids)
|
|
167
|
+
except (
|
|
168
|
+
EveCharacter.DoesNotExist,
|
|
169
|
+
AttributeError,
|
|
170
|
+
CharacterOwnership.DoesNotExist,
|
|
171
|
+
):
|
|
172
|
+
pass
|
|
173
|
+
return [self.entity_id]
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def create_button(self):
|
|
177
|
+
"""Generate the URL for character details."""
|
|
178
|
+
title = _("View Details")
|
|
179
|
+
button_html = f"<button class='btn btn-primary btn-sm btn-square' data-bs-toggle='modal' data-bs-target='#modalViewCharacterContainer' data-ajax_url='{self.details_url}' title='{title}' data-tooltip-toggle='ledger-tooltip'><span class='fas fa-info'></span></button>"
|
|
180
|
+
return mark_safe(button_html)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class LedgerCore:
|
|
184
|
+
"""Core View Helper for Ledger."""
|
|
185
|
+
|
|
186
|
+
def __init__(self, year=None, month=None, day=None):
|
|
187
|
+
self.date_info = {"year": year, "month": month, "day": day}
|
|
188
|
+
self.ledger_type = "ledger"
|
|
189
|
+
|
|
190
|
+
# If all are None, default to 'month' view
|
|
191
|
+
if year is None and month is None and day is None:
|
|
192
|
+
self.view = "month"
|
|
193
|
+
else:
|
|
194
|
+
self.view = "day" if day else "month" if month else "year"
|
|
195
|
+
|
|
196
|
+
self.journal = None
|
|
197
|
+
self.mining = None
|
|
198
|
+
self.billboard = BillboardSystem(self.view)
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def year(self):
|
|
202
|
+
return self.date_info["year"]
|
|
203
|
+
|
|
204
|
+
@property
|
|
205
|
+
def month(self):
|
|
206
|
+
return self.date_info["month"]
|
|
207
|
+
|
|
208
|
+
@property
|
|
209
|
+
def day(self):
|
|
210
|
+
return self.date_info["day"]
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def filter_date(self):
|
|
214
|
+
"""
|
|
215
|
+
Generate a date filter for the ledger based on year, month, and day.
|
|
216
|
+
Returns:
|
|
217
|
+
Q: A Django Q object representing the date filter.
|
|
218
|
+
"""
|
|
219
|
+
now = timezone.now()
|
|
220
|
+
# If all are None, use current year and month
|
|
221
|
+
if self.year is None and self.month is None and self.day is None:
|
|
222
|
+
filter_date = Q(date__year=now.year) & Q(date__month=now.month)
|
|
223
|
+
else:
|
|
224
|
+
filter_date = (
|
|
225
|
+
Q(date__year=self.year) if self.year else Q(date__year=now.year)
|
|
226
|
+
)
|
|
227
|
+
if self.month:
|
|
228
|
+
filter_date &= Q(date__month=self.month)
|
|
229
|
+
if self.day:
|
|
230
|
+
filter_date &= Q(date__day=self.day)
|
|
231
|
+
return filter_date
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
def get_details_title(self):
|
|
235
|
+
"""
|
|
236
|
+
Generate a title for the details view based on the date information.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
str: A formatted string representing the date or a default title.
|
|
240
|
+
"""
|
|
241
|
+
if self.year and self.month and self.day:
|
|
242
|
+
return f"{self.year:04d}-{self.month:02d}-{self.day:02d}"
|
|
243
|
+
if self.year and self.month:
|
|
244
|
+
return f"{self.year:04d}-{self.month:02d}"
|
|
245
|
+
if self.year:
|
|
246
|
+
return f"{self.year:04d}"
|
|
247
|
+
return "Character Ledger Details"
|
|
248
|
+
|
|
249
|
+
@property
|
|
250
|
+
def auth_accounts(self):
|
|
251
|
+
"""Get all user accounts with a main character."""
|
|
252
|
+
return (
|
|
253
|
+
UserProfile.objects.filter(
|
|
254
|
+
main_character__isnull=False,
|
|
255
|
+
)
|
|
256
|
+
.prefetch_related(
|
|
257
|
+
"user__profile__main_character",
|
|
258
|
+
)
|
|
259
|
+
.order_by(
|
|
260
|
+
"user__profile__main_character__character_name",
|
|
261
|
+
)
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
@property
|
|
265
|
+
def auth_character_ids(self) -> set:
|
|
266
|
+
"""Get all account Character IDs from Alliance Auth."""
|
|
267
|
+
account_character_ids = set()
|
|
268
|
+
for account in self.auth_accounts:
|
|
269
|
+
alts = account.user.character_ownerships.all()
|
|
270
|
+
account_character_ids.update(
|
|
271
|
+
alts.values_list("character__character_id", flat=True)
|
|
272
|
+
)
|
|
273
|
+
return account_character_ids
|
|
274
|
+
|
|
275
|
+
def _get_ledger_journal_hash(self, journal: list[str]) -> str:
|
|
276
|
+
"""Generate a hash for the ledger journal."""
|
|
277
|
+
return md5(",".join(str(x) for x in sorted(journal)).encode()).hexdigest()
|
|
278
|
+
|
|
279
|
+
def _get_ledger_hash(self, header_key: str) -> str:
|
|
280
|
+
"""Generate a hash for the ledger journal."""
|
|
281
|
+
return md5(header_key.encode()).hexdigest()
|
|
282
|
+
|
|
283
|
+
def _get_ledger_header(
|
|
284
|
+
self, ledger_args: str, year: int, month: int, day: int
|
|
285
|
+
) -> str:
|
|
286
|
+
"""Generate a header string for the ledger."""
|
|
287
|
+
return f"{ledger_args}_{year}_{month}_{day}"
|
|
288
|
+
|
|
289
|
+
def _build_ledger_cache_key(self, header_key: str) -> str:
|
|
290
|
+
"""Build a cache key for the ledger."""
|
|
291
|
+
return f"{LEDGER_CACHE_KEY}-{self._get_ledger_hash(header_key)}"
|
|
292
|
+
|
|
293
|
+
def _calculate_totals(self, ledger) -> dict:
|
|
294
|
+
"""
|
|
295
|
+
Calculate the total amounts for each category in the ledger.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
ledger (list or dict): The ledger data to calculate totals from.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
dict: A dictionary containing the totals for each category.
|
|
302
|
+
"""
|
|
303
|
+
totals = {
|
|
304
|
+
"bounty": Decimal(0),
|
|
305
|
+
"ess": Decimal(0),
|
|
306
|
+
"costs": Decimal(0),
|
|
307
|
+
"mining": Decimal(0),
|
|
308
|
+
"miscellaneous": Decimal(0),
|
|
309
|
+
"total": Decimal(0),
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if not ledger:
|
|
313
|
+
return totals
|
|
314
|
+
|
|
315
|
+
if isinstance(ledger, dict):
|
|
316
|
+
ledger = [ledger]
|
|
317
|
+
|
|
318
|
+
for total in ledger:
|
|
319
|
+
|
|
320
|
+
if total is None:
|
|
321
|
+
continue
|
|
322
|
+
|
|
323
|
+
totals["bounty"] += total["ledger"].get("bounty", 0)
|
|
324
|
+
totals["ess"] += total["ledger"].get("ess", 0)
|
|
325
|
+
totals["costs"] += total["ledger"].get("costs", 0)
|
|
326
|
+
totals["mining"] += total["ledger"].get("mining", 0)
|
|
327
|
+
totals["miscellaneous"] += total["ledger"].get("miscellaneous", 0)
|
|
328
|
+
totals["total"] += total["ledger"].get("total", 0)
|
|
329
|
+
return totals
|
|
330
|
+
|
|
331
|
+
def create_url(self, viewname: str, **kwargs):
|
|
332
|
+
"""
|
|
333
|
+
Create a URL for the given view and entity using kwargs.
|
|
334
|
+
Args:
|
|
335
|
+
viewname: The name of the view to create the URL for.
|
|
336
|
+
kwargs: All needed parameters for the URL (e.g. character_id, corporation_id, etc.)
|
|
337
|
+
Returns:
|
|
338
|
+
A URL string for the specified view.
|
|
339
|
+
"""
|
|
340
|
+
if self.year and self.month and self.day:
|
|
341
|
+
return reverse(
|
|
342
|
+
f"ledger:{viewname}",
|
|
343
|
+
kwargs={
|
|
344
|
+
**kwargs,
|
|
345
|
+
"year": self.year,
|
|
346
|
+
"month": self.month,
|
|
347
|
+
"day": self.day,
|
|
348
|
+
},
|
|
349
|
+
)
|
|
350
|
+
if self.year and self.month:
|
|
351
|
+
return reverse(
|
|
352
|
+
f"ledger:{viewname}",
|
|
353
|
+
kwargs={
|
|
354
|
+
**kwargs,
|
|
355
|
+
"year": self.year,
|
|
356
|
+
"month": self.month,
|
|
357
|
+
},
|
|
358
|
+
)
|
|
359
|
+
if self.year:
|
|
360
|
+
return reverse(
|
|
361
|
+
f"ledger:{viewname}",
|
|
362
|
+
kwargs={**kwargs, "year": self.year},
|
|
363
|
+
)
|
|
364
|
+
return reverse(
|
|
365
|
+
f"ledger:{viewname}",
|
|
366
|
+
kwargs={
|
|
367
|
+
**kwargs,
|
|
368
|
+
"year": timezone.now().year,
|
|
369
|
+
"month": timezone.now().month,
|
|
370
|
+
},
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
def create_view_data(self, viewname: str, **kwargs) -> dict:
|
|
374
|
+
"""
|
|
375
|
+
Create view data for the ledger using kwargs.
|
|
376
|
+
Args:
|
|
377
|
+
viewname (str): The name of the view to create the URL for.
|
|
378
|
+
kwargs: All needed parameters for the URL (e.g. character_id, corporation_id, etc.)
|
|
379
|
+
Returns:
|
|
380
|
+
dict: A dictionary containing the type, date, and details URL.
|
|
381
|
+
"""
|
|
382
|
+
return {
|
|
383
|
+
"type": self.ledger_type,
|
|
384
|
+
"date": {
|
|
385
|
+
"current": {
|
|
386
|
+
"year": timezone.now().year,
|
|
387
|
+
"month": timezone.now().month,
|
|
388
|
+
"day": timezone.now().day,
|
|
389
|
+
},
|
|
390
|
+
"year": self.year,
|
|
391
|
+
"month": self.month,
|
|
392
|
+
"day": self.day,
|
|
393
|
+
},
|
|
394
|
+
"details_url": self.create_url(
|
|
395
|
+
viewname=viewname,
|
|
396
|
+
**kwargs,
|
|
397
|
+
),
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
# pylint: disable=too-many-locals
|
|
401
|
+
def _generate_amounts(
|
|
402
|
+
self,
|
|
403
|
+
income_types: list,
|
|
404
|
+
entity: LedgerEntity,
|
|
405
|
+
is_old_ess: bool = False,
|
|
406
|
+
char_ids: list = None,
|
|
407
|
+
) -> dict:
|
|
408
|
+
"""Generate amounts for the entity based on income types and reference types."""
|
|
409
|
+
amounts = {}
|
|
410
|
+
|
|
411
|
+
ref_types = RefTypeManager.get_all_categories()
|
|
412
|
+
|
|
413
|
+
# Bounty Income
|
|
414
|
+
if not entity.entity_id == 1000125: # Remove Concord Bountys
|
|
415
|
+
bounty_income = self.journal.aggregate_bounty()
|
|
416
|
+
if bounty_income > 0:
|
|
417
|
+
amounts["bounty_income"] = {
|
|
418
|
+
"total_amount": bounty_income,
|
|
419
|
+
"ref_types": ["bounty_prizes"],
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if isinstance(entity.entity, EveCharacter):
|
|
423
|
+
# Mining Income
|
|
424
|
+
mining_income = self.mining.aggregate_mining()
|
|
425
|
+
if mining_income > 0:
|
|
426
|
+
amounts["mining_income"] = {
|
|
427
|
+
"total_amount": mining_income,
|
|
428
|
+
"ref_types": ["mining"],
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
# ESS Income (nur wenn bounty_income existiert)
|
|
432
|
+
ess_income = (
|
|
433
|
+
bounty_income * Decimal(0.667)
|
|
434
|
+
if is_old_ess and bounty_income
|
|
435
|
+
else self.journal.aggregate_ess()
|
|
436
|
+
)
|
|
437
|
+
if ess_income > 0:
|
|
438
|
+
amounts["ess_income"] = {
|
|
439
|
+
"total_amount": ess_income,
|
|
440
|
+
"ref_types": ["ess_escrow_transfer"],
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
# Income/Cost Ref Types (DRY)
|
|
444
|
+
for ref_type, value in ref_types.items():
|
|
445
|
+
ref_type_name = ref_type.lower()
|
|
446
|
+
for kind, income_flag in (("income", True), ("cost", False)):
|
|
447
|
+
kwargs = {"ref_type": value, "income": income_flag}
|
|
448
|
+
kwargs = RefTypeManager.special_cases_details(
|
|
449
|
+
value, entity, kwargs, journal_type=entity.type, char_ids=char_ids
|
|
450
|
+
)
|
|
451
|
+
agg = self.journal.aggregate_ref_type(**kwargs)
|
|
452
|
+
if (income_flag and agg > 0) or (not income_flag and agg < 0):
|
|
453
|
+
amounts[f"{ref_type_name}_{kind}"] = {
|
|
454
|
+
"total_amount": agg,
|
|
455
|
+
"ref_types": value,
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
# Summary (exclude mining_income)
|
|
459
|
+
summary = sum(
|
|
460
|
+
amount["total_amount"]
|
|
461
|
+
for key, amount in amounts.items()
|
|
462
|
+
if key != "mining_income"
|
|
463
|
+
and isinstance(amount, dict)
|
|
464
|
+
and "total_amount" in amount
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
if summary == 0:
|
|
468
|
+
return None
|
|
469
|
+
|
|
470
|
+
amounts["summary"] = {
|
|
471
|
+
"total_amount": summary,
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
# Dynamische Income/Cost-Typen für das Template
|
|
475
|
+
income_types += [
|
|
476
|
+
(f"{ref_type.lower()}_income", _(ref_type.replace("_", " ").title()))
|
|
477
|
+
for ref_type in ref_types
|
|
478
|
+
]
|
|
479
|
+
cost_types = [
|
|
480
|
+
(f"{ref_type.lower()}_cost", _(ref_type.replace("_", " ").title()))
|
|
481
|
+
for ref_type in ref_types
|
|
482
|
+
]
|
|
483
|
+
amounts["income_types"] = income_types
|
|
484
|
+
amounts["cost_types"] = cost_types
|
|
485
|
+
return amounts
|
|
486
|
+
|
|
487
|
+
def _create_corporation_details(self, entity: LedgerEntity) -> dict:
|
|
488
|
+
"""Create the corporation amounts for the Information View."""
|
|
489
|
+
# NOTE (can only used if setup_ledger is defined in the subclass)
|
|
490
|
+
self.setup_ledger(entity=entity) # pylint: disable=no-member
|
|
491
|
+
|
|
492
|
+
income_types = [
|
|
493
|
+
("bounty_income", _("Bounty")),
|
|
494
|
+
("ess_income", _("Encounter Surveillance System")),
|
|
495
|
+
]
|
|
496
|
+
amounts = self._generate_amounts(income_types=income_types, entity=entity)
|
|
497
|
+
return amounts
|
|
498
|
+
|
|
499
|
+
# pylint: disable=no-member
|
|
500
|
+
def _create_character_details(self) -> dict:
|
|
501
|
+
"""
|
|
502
|
+
Create the character amounts for the Information View.
|
|
503
|
+
Only work with CharacterData Class
|
|
504
|
+
"""
|
|
505
|
+
if not self.character:
|
|
506
|
+
raise ValueError("No Character Data found.")
|
|
507
|
+
|
|
508
|
+
# NOTE (can only used if setup_ledger is defined in the subclass)
|
|
509
|
+
self.setup_ledger(self.character)
|
|
510
|
+
|
|
511
|
+
entity = LedgerEntity(
|
|
512
|
+
entity_id=self.character.eve_character.character_id,
|
|
513
|
+
character_obj=self.character.eve_character,
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
income_types = [
|
|
517
|
+
("bounty_income", _("Bounty")),
|
|
518
|
+
("ess_income", _("Encounter Surveillance System")),
|
|
519
|
+
("mining_income", _("Mining")),
|
|
520
|
+
]
|
|
521
|
+
amounts = self._generate_amounts(
|
|
522
|
+
income_types=income_types,
|
|
523
|
+
entity=entity,
|
|
524
|
+
is_old_ess=self.is_old_ess,
|
|
525
|
+
char_ids=self.alts_ids,
|
|
526
|
+
)
|
|
527
|
+
return amounts
|
|
528
|
+
|
|
529
|
+
def _add_average_details(self, request, amounts, day: int = None):
|
|
530
|
+
"""Add average details to the amounts dictionary, skipping if no data or total is 0."""
|
|
531
|
+
if amounts is None:
|
|
532
|
+
return None
|
|
533
|
+
|
|
534
|
+
avg = day if day else timezone.now().day
|
|
535
|
+
if request.GET.get("all", False):
|
|
536
|
+
avg = 365
|
|
537
|
+
|
|
538
|
+
for key in amounts:
|
|
539
|
+
if (
|
|
540
|
+
isinstance(amounts[key], dict)
|
|
541
|
+
and "total_amount" in amounts[key]
|
|
542
|
+
and amounts[key]["total_amount"] not in (None, 0, 0.0, Decimal(0))
|
|
543
|
+
):
|
|
544
|
+
total = amounts[key]["total_amount"]
|
|
545
|
+
amounts[key]["average_day"] = total / avg
|
|
546
|
+
amounts[key]["average_hour"] = total / avg / 24
|
|
547
|
+
amounts[key]["average_tick"] = total / 20
|
|
548
|
+
amounts[key]["current_day_tick"] = (
|
|
549
|
+
amounts[key].get("total_amount_day", 0) / 20
|
|
550
|
+
)
|
|
551
|
+
amounts[key]["average_day_tick"] = total / avg / 20
|
|
552
|
+
amounts[key]["average_hour_tick"] = total / avg / 24 / 20
|
|
553
|
+
return amounts
|
|
554
|
+
|
|
555
|
+
def _build_xy_chart(self, title: str):
|
|
556
|
+
"""Build the XY chart for the billboard."""
|
|
557
|
+
if not self.billboard.results:
|
|
558
|
+
return
|
|
559
|
+
|
|
560
|
+
xy_data, categories = self.billboard.generate_xy_series()
|
|
561
|
+
self.billboard.create_xy_chart(
|
|
562
|
+
title=title,
|
|
563
|
+
categories=categories,
|
|
564
|
+
series=xy_data,
|
|
565
|
+
)
|