aa-fleetfinder 2.7.0__py3-none-any.whl → 2.7.2__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.

Potentially problematic release.


This version of aa-fleetfinder might be problematic. Click here for more details.

Files changed (38) hide show
  1. {aa_fleetfinder-2.7.0.dist-info → aa_fleetfinder-2.7.2.dist-info}/METADATA +1 -1
  2. {aa_fleetfinder-2.7.0.dist-info → aa_fleetfinder-2.7.2.dist-info}/RECORD +38 -32
  3. fleetfinder/__init__.py +1 -1
  4. fleetfinder/locale/cs_CZ/LC_MESSAGES/django.po +30 -20
  5. fleetfinder/locale/de/LC_MESSAGES/django.mo +0 -0
  6. fleetfinder/locale/de/LC_MESSAGES/django.po +35 -33
  7. fleetfinder/locale/django.pot +31 -21
  8. fleetfinder/locale/es/LC_MESSAGES/django.po +30 -20
  9. fleetfinder/locale/fr_FR/LC_MESSAGES/django.mo +0 -0
  10. fleetfinder/locale/fr_FR/LC_MESSAGES/django.po +69 -55
  11. fleetfinder/locale/it_IT/LC_MESSAGES/django.po +30 -20
  12. fleetfinder/locale/ja/LC_MESSAGES/django.po +30 -20
  13. fleetfinder/locale/ko_KR/LC_MESSAGES/django.po +30 -20
  14. fleetfinder/locale/nl_NL/LC_MESSAGES/django.po +30 -20
  15. fleetfinder/locale/pl_PL/LC_MESSAGES/django.po +30 -20
  16. fleetfinder/locale/ru/LC_MESSAGES/django.po +30 -20
  17. fleetfinder/locale/sk/LC_MESSAGES/django.po +30 -20
  18. fleetfinder/locale/uk/LC_MESSAGES/django.mo +0 -0
  19. fleetfinder/locale/uk/LC_MESSAGES/django.po +52 -52
  20. fleetfinder/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
  21. fleetfinder/locale/zh_Hans/LC_MESSAGES/django.po +33 -23
  22. fleetfinder/static/fleetfinder/js/fleetfinder-dashboard.js +86 -0
  23. fleetfinder/static/fleetfinder/js/fleetfinder-dashboard.min.js +2 -0
  24. fleetfinder/static/fleetfinder/js/fleetfinder-dashboard.min.js.map +1 -0
  25. fleetfinder/static/fleetfinder/js/fleetfinder-fleet-details.js +154 -0
  26. fleetfinder/static/fleetfinder/js/fleetfinder-fleet-details.min.js +2 -0
  27. fleetfinder/static/fleetfinder/js/fleetfinder-fleet-details.min.js.map +1 -0
  28. fleetfinder/static/fleetfinder/js/fleetfinder.min.js +1 -1
  29. fleetfinder/static/fleetfinder/js/fleetfinder.min.js.map +1 -1
  30. fleetfinder/tasks.py +13 -5
  31. fleetfinder/templates/fleetfinder/base.html +11 -0
  32. fleetfinder/templates/fleetfinder/bundles/js/fleetfinder-js.html +6 -0
  33. fleetfinder/templates/fleetfinder/dashboard.html +8 -83
  34. fleetfinder/templates/fleetfinder/fleet-details.html +22 -150
  35. fleetfinder/tests/test_views.py +63 -0
  36. fleetfinder/views.py +32 -3
  37. {aa_fleetfinder-2.7.0.dist-info → aa_fleetfinder-2.7.2.dist-info}/WHEEL +0 -0
  38. {aa_fleetfinder-2.7.0.dist-info → aa_fleetfinder-2.7.2.dist-info}/licenses/LICENSE +0 -0
fleetfinder/tasks.py CHANGED
@@ -306,10 +306,10 @@ def get_fleet_composition( # pylint: disable=too-many-locals
306
306
 
307
307
  try:
308
308
  fleet = Fleet.objects.get(fleet_id=fleet_id)
309
- except Fleet.DoesNotExist:
309
+ except Fleet.DoesNotExist as exc:
310
310
  logger.error(f"Fleet with ID {fleet_id} not found")
311
311
 
312
- return None
312
+ raise Fleet.DoesNotExist(f"Fleet with ID {fleet_id} not found.") from exc
313
313
 
314
314
  logger.info(
315
315
  f'Getting fleet composition for fleet "{fleet.name}" '
@@ -342,8 +342,14 @@ def get_fleet_composition( # pylint: disable=too-many-locals
342
342
  f"Found {len(all_ids)} unique IDs to fetch names for in fleet {fleet_id}"
343
343
  )
344
344
 
345
- # Process IDs in chunks to avoid ESI limits
346
- chunk_size = 500
345
+ # Process IDs in chunks of 1000 to avoid ESI limits.
346
+ # ESI has a limit of 1000 IDs per request, so we will chunk the requests,
347
+ # even though there is a theoretical limit of 768 unique IDs per fleet,
348
+ # so we never should hit the ESI limit.
349
+ # But to be on the safe side, we will chunk the requests in case CCP decides
350
+ # to change the fleet limit in the future, we will use a chunk size of 1000,
351
+ # which is the maximum allowed by ESI for the `post_universe_names` endpoint.
352
+ chunk_size = 1000
347
353
  ids_to_name = []
348
354
  all_ids_list = list(all_ids)
349
355
 
@@ -376,4 +382,6 @@ def get_fleet_composition( # pylint: disable=too-many-locals
376
382
  except Exception as e: # pylint: disable=broad-exception-caught
377
383
  logger.error(f"Failed to get fleet composition for fleet {fleet_id}: {e}")
378
384
 
379
- return None
385
+ raise RuntimeError(
386
+ f"Failed to get fleet composition for fleet {fleet_id} : {str(e)}"
387
+ ) from e
@@ -1,6 +1,7 @@
1
1
  {% extends "allianceauth/base-bs5.html" %}
2
2
 
3
3
  {% load i18n %}
4
+ {% load aa_i18n %}
4
5
 
5
6
  {% block page_title %}
6
7
  {% translate "Fleet Finder" %}
@@ -22,6 +23,16 @@
22
23
  {% block content %}
23
24
  <div class="aa-fleetfinder">
24
25
  <div class="aa-fleetfinder-body">
26
+ {% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %}
27
+
28
+ <script>
29
+ const aaFleetFinderSettings = {
30
+ dataTables: {
31
+ languageUrl: "{{ DT_LANG_PATH }}",
32
+ datetimeFormat: 'YYYY-MM-DD, HH:mm',
33
+ }
34
+ }
35
+ </script>
25
36
  {% block aa_fleetfinder_body %}{% endblock %}
26
37
  </div>
27
38
 
@@ -1,3 +1,9 @@
1
1
  {% load sri %}
2
2
 
3
3
  {% sri_static "fleetfinder/js/fleetfinder.min.js" %}
4
+
5
+ {% if view %}
6
+ {% with "fleetfinder/js/fleetfinder-"|add:view|add:".min.js" as script_path %}
7
+ {% sri_static script_path %}
8
+ {% endwith %}
9
+ {% endif %}
@@ -38,91 +38,16 @@
38
38
  {% block extra_javascript %}
39
39
  {% include "bundles/datatables-js-bs5.html" %}
40
40
  {% include "bundles/moment-js.html" with locale=True %}
41
- {% include "fleetfinder/bundles/js/fleetfinder-js.html" %}
42
-
43
- {% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %}
44
41
 
45
42
  <script>
46
- /* global fetchGet, fleetfinderBootstrapTooltip, DataTable */
47
- 'use strict';
48
-
49
- $(document).ready(() => {
50
- const DATETIME_FORMAT = 'YYYY-MM-DD, HH:mm';
51
- const table_fleet_overview = $('#table_available-fleets');
52
- let dataTable = null;
53
-
54
- /**
55
- * Initialize or update the datatable.
56
- * If the table already exists, it will be updated with new data.
57
- * If it does not exist, a new DataTable will be created.
58
- *
59
- * @param {Object} data - The fleet data to populate the table.
60
- */
61
- const initializeOrUpdateTable = (data) => {
62
- if (dataTable) {
63
- // Update existing table
64
- dataTable.clear().rows.add(data).draw();
65
- } else {
66
- // Initialize new table
67
- dataTable = table_fleet_overview.DataTable({
68
- language: {
69
- url: '{{ DT_LANG_PATH }}'
70
- },
71
- data: data,
72
- columns: [
73
- {
74
- data: 'fleet_commander',
75
- render: {
76
- _: 'html',
77
- sort: 'sort'
78
- }
79
- },
80
- {
81
- data: 'fleet_name'
82
- },
83
- {
84
- data: 'created_at',
85
- },
86
- {
87
- data: 'actions',
88
- className: 'text-end',
89
- },
90
- ],
91
- columnDefs: [
92
- {
93
- targets: 2,
94
- render: DataTable.render.date(DATETIME_FORMAT)
95
- },
96
- {
97
- orderable: false,
98
- targets: [3]
99
- },
100
- ],
101
- order: [[0, 'asc']],
102
- paging: false
103
- });
43
+ const aaFleetFinderSettingsOverride = {
44
+ dataTables: {
45
+ url: {
46
+ dashboard: '{% url "fleetfinder:ajax_dashboard" %}'
104
47
  }
105
-
106
- // Initialize Bootstrap tooltips
107
- fleetfinderBootstrapTooltip({selector: '#table_available-fleets'});
108
- };
109
-
110
- /**
111
- * Fetch and update fleet data
112
- */
113
- const fetchFleetData = () => {
114
- fetchGet({url: '{% url "fleetfinder:ajax_dashboard" %}'})
115
- .then(initializeOrUpdateTable)
116
- .catch((error) => {
117
- console.error('Error fetching fleet data:', error);
118
- });
119
- };
120
-
121
- // Initial load
122
- fetchFleetData();
123
-
124
- // Refresh every 30 seconds
125
- setInterval(fetchFleetData, 30000);
126
- });
48
+ }
49
+ };
127
50
  </script>
51
+
52
+ {% include "fleetfinder/bundles/js/fleetfinder-js.html" with view="dashboard" %}
128
53
  {% endblock %}
@@ -8,6 +8,10 @@
8
8
  {% endblock %}
9
9
 
10
10
  {% block aa_fleetfinder_body %}
11
+ {% include "framework/header/page-header.html" with title=fleet.name subtitle=fleet.fleet_commander.character_name %}
12
+
13
+ <div class="alert alert-warning d-none" id="fleetfinder-fleet-details-warning"></div>
14
+
11
15
  <div class="row">
12
16
  <div class="col-lg-6 col-lg-push-6">
13
17
  <div class="card card-primary">
@@ -19,7 +23,7 @@
19
23
 
20
24
  <div class="card-body">
21
25
  <div class="table-responsive">
22
- <table class="table table-striped table-hover table-vertical-middle w-100" id="table_fleet_composition">
26
+ <table class="table table-striped table-hover table-vertical-middle w-100" id="table-fleet-composition">
23
27
  <thead>
24
28
  <tr>
25
29
  <th>{% translate "Ship class" %}</th>
@@ -44,7 +48,7 @@
44
48
 
45
49
  <div class="card-body">
46
50
  <div class="table-responsive">
47
- <table class="table table-striped table-hover table-vertical-middle w-100" id="table_fleet_members">
51
+ <table class="table table-striped table-hover table-vertical-middle w-100" id="table-fleet-members">
48
52
  <thead>
49
53
  <tr>
50
54
  <th>{% translate "Name" %}</th>
@@ -72,159 +76,27 @@
72
76
 
73
77
  {% block extra_javascript %}
74
78
  {% include "bundles/datatables-js-bs5.html" %}
75
- {% include "fleetfinder/bundles/js/fleetfinder-js.html" %}
76
-
77
- {% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %}
78
79
 
79
80
  {% translate "Fleet boss" as l10nFleetBoss %}
80
81
  {% translate "Kick member from fleet" as l10nKickMemberFromFleet %}
81
82
  {% translate "An unknown error occurred." as l10nUnknownError %}
82
83
 
83
84
  <script>
84
- $(document).ready(() => {
85
- /* DataTables
86
- ------------------------------------------------------------------------- */
87
- const table_fleet_members = $('#table_fleet_members');
88
- const table_fleet_composition = $('#table_fleet_composition');
89
- const url = {
90
- kickFleetMember: '{% url "fleetfinder:ajax_fleet_kick_member" fleet_id %}',
91
- fleetDetails: '{% url "fleetfinder:ajax_fleet_details" fleet_id %}'
92
- };
93
- const dataTableConfig = {
94
- language: {
95
- url: '{{ DT_LANG_PATH }}'
96
- },
97
- paging: false,
98
- destroy: true
99
- };
100
-
101
- const populateDatatables = () => {
102
- fetchGet({
103
- url: url.fleetDetails,
104
- })
105
- .then((data) => {
106
- table_fleet_members.DataTable({
107
- ...dataTableConfig,
108
- data: data.fleet_member,
109
- columns: [
110
- {
111
- render: (data, type, row) => {
112
- const fwIcon = '<i class="fa-solid fa-star"></i>';
113
-
114
- return row.is_fleet_boss
115
- ? `${row.character_name} <sup data-bs-tooltip="aa-fleetfinder" title="{{ l10nFleetBoss|escapejs }}"><small>${fwIcon}</small></sup>`
116
- : row.character_name;
117
- }
118
- },
119
- {data: 'ship_type_name'},
120
- {data: 'solar_system_name'},
121
- {
122
- render: (data, type, row) => {
123
- const fwIcon = '<i class="fa-solid fa-user-minus"></i>';
124
- const dataAttributes = Object.entries({
125
- 'data-character-id': row.character_id,
126
- 'data-character-name': row.character_name,
127
- 'data-bs-toggle': 'modal',
128
- 'data-bs-target': '#kick-fleet-member',
129
- 'data-bs-tooltip': 'aa-fleetfinder'
130
- }).map(([key, value]) => {
131
- return `${key}="${value}"`;
132
- }).join(' ');
133
-
134
- return row.is_fleet_boss
135
- ? ''
136
- : `<button type="button" class="btn btn-sm btn-danger" ${dataAttributes} title="{{ l10nKickMemberFromFleet|escapejs }}">${fwIcon}</button>`;
137
- },
138
- orderable: false,
139
- searchable: false,
140
- width: '50px',
141
- className: 'text-end'
142
- }
143
- ],
144
- createdRow: (row, data, rowIndex) => {
145
- $(row).attr('data-row-id', rowIndex);
146
- $(row).attr('data-character-id', data.character_id);
147
- }
148
- });
149
-
150
- table_fleet_composition.DataTable({
151
- ...dataTableConfig,
152
- data: data.fleet_composition,
153
- columns: [
154
- {data: 'ship_type_name'},
155
- {data: 'number', className: 'text-right', width: '100px'}
156
- ],
157
- order: [[1, 'desc']]
158
- });
159
- })
160
- .then(() => {
161
- // Initialize tooltips for the kick buttons
162
- fleetfinderBootstrapTooltip({selector: '#table_fleet_members'});
163
- })
164
- .catch((error) => {
165
- console.error('Error fetching fleet details:', error);
166
- });
167
- };
168
-
169
- populateDatatables();
170
-
171
- setInterval(populateDatatables, 30000);
172
-
173
- /* Modals
174
- ------------------------------------------------------------------------- */
175
- // Handle the kick fleet member modal
176
- $('#kick-fleet-member')
177
- .on('show.bs.modal', (event) => {
178
- const button = $(event.relatedTarget);
179
- const characterId = button.data('character-id');
180
- const characterName = button.data('character-name');
181
- const link = url.kickFleetMember;
182
- const csrfToken = '{{ csrf_token }}';
183
-
184
- // Populate the modal content
185
- $('#kick-fleet-member-character-name').text(characterName);
186
-
187
- $('#modal-button-confirm-kick-fleet-member')
188
- // Remove any previous click handlers to avoid multiple bindings
189
- .off('click.kickMember')
190
- // Bind the click event for the confirmation button
191
- .on('click.kickMember', () => {
192
- fetchPost({
193
- url: link,
194
- csrfToken: csrfToken,
195
- payload: {
196
- memberId: characterId
197
- },
198
- responseIsJson: true
199
- })
200
- .then((data) => {
201
- if (data.success) {
202
- populateDatatables();
203
-
204
- $('#kick-fleet-member').modal('hide');
205
- } else {
206
- $('#kick-fleet-member .modal-kick-fleet-member-error')
207
- .removeClass('d-none')
208
- .find('.modal-kick-fleet-member-error-message')
209
- .text(data.error || '{{ l10nUnknownError|escapejs }}');
210
- }
211
- })
212
- .catch((error) => {
213
- console.error('Error kicking fleet member:', error);
214
- });
215
- });
216
- })
217
- .on('hide.bs.modal', () => {
218
- // Reset modal content
219
- $('#kick-fleet-member-character-name').empty();
220
- $('#kick-fleet-member .modal-kick-fleet-member-error')
221
- .addClass('d-none')
222
- .find('.modal-kick-fleet-member-error-message')
223
- .empty();
224
-
225
- // Clean up event handler
226
- $('#modal-button-confirm-kick-fleet-member').off('click.kickMember');
227
- });
228
- });
85
+ const aaFleetFinderSettingsOverride = {
86
+ dataTables: {
87
+ url: {
88
+ fleetDetails: '{% url "fleetfinder:ajax_fleet_details" fleet.fleet_id %}',
89
+ kickFleetMember: '{% url "fleetfinder:ajax_fleet_kick_member" fleet.fleet_id %}'
90
+ }
91
+ },
92
+ l10n: {
93
+ fleetBoss: '{{ l10nFleetBoss|escapejs }}',
94
+ kickMemberFromFleet: '{{ l10nKickMemberFromFleet|escapejs }}',
95
+ unknownError: '{{ l10nUnknownError|escapejs }}'
96
+ },
97
+ csrfToken: '{{ csrf_token }}'
98
+ };
229
99
  </script>
100
+
101
+ {% include "fleetfinder/bundles/js/fleetfinder-js.html" with view="fleet-details" %}
230
102
  {% endblock %}
@@ -402,6 +402,21 @@ class TestFleetDetailsView(FleetfinderTestViews):
402
402
 
403
403
  self.assertEqual(response.status_code, HTTPStatus.FOUND)
404
404
 
405
+ def test_fleet_redirects_to_dashboard_if_not_found(self):
406
+ """
407
+ Test that the fleet_details view redirects to the dashboard if the fleet does not exist.
408
+
409
+ :return:
410
+ :rtype:
411
+ """
412
+
413
+ self.client.force_login(self.user_with_manage_perms)
414
+
415
+ response = self.client.get(reverse("fleetfinder:fleet_details", args=[123]))
416
+
417
+ self.assertEqual(response.status_code, HTTPStatus.FOUND)
418
+ self.assertEqual(response.url, reverse("fleetfinder:dashboard"))
419
+
405
420
 
406
421
  class TestAjaxFleetDetailsView(FleetfinderTestViews):
407
422
  """
@@ -471,3 +486,51 @@ class TestAjaxFleetDetailsView(FleetfinderTestViews):
471
486
 
472
487
  self.assertEqual(response.status_code, HTTPStatus.OK)
473
488
  self.assertEqual(json.loads(response.content), expected_fleet_composition)
489
+
490
+ @patch("fleetfinder.views.get_fleet_composition")
491
+ def test_returns_error_when_fleet_does_not_exist(self, mock_get_fleet_composition):
492
+ """
493
+ Test that the ajax_fleet_details view returns an error when the fleet does not exist.
494
+
495
+ :param mock_get_fleet_composition:
496
+ :type mock_get_fleet_composition:
497
+ :return:
498
+ :rtype:
499
+ """
500
+
501
+ mock_get_fleet_composition.side_effect = Fleet.DoesNotExist
502
+
503
+ self.client.force_login(user=self.user_with_manage_perms)
504
+
505
+ url = reverse("fleetfinder:ajax_fleet_details", args=[123])
506
+ response = self.client.get(url)
507
+
508
+ self.assertEqual(response.status_code, HTTPStatus.OK)
509
+ self.assertJSONEqual(
510
+ response.content,
511
+ {"error": "Fleet with ID 123 does not exist."},
512
+ )
513
+
514
+ @patch("fleetfinder.views.get_fleet_composition")
515
+ def test_returns_error_when_runtime_error_occurs(self, mock_get_fleet_composition):
516
+ """
517
+ Test that the ajax_fleet_details view returns an error when a runtime error occurs.
518
+
519
+ :param mock_get_fleet_composition:
520
+ :type mock_get_fleet_composition:
521
+ :return:
522
+ :rtype:
523
+ """
524
+
525
+ mock_get_fleet_composition.side_effect = RuntimeError("Unexpected error")
526
+
527
+ self.client.force_login(user=self.user_with_manage_perms)
528
+
529
+ url = reverse("fleetfinder:ajax_fleet_details", args=[123])
530
+ response = self.client.get(url)
531
+
532
+ self.assertEqual(response.status_code, HTTPStatus.OK)
533
+ self.assertJSONEqual(
534
+ response.content,
535
+ {"error": "Error retrieving fleet composition: Unexpected error"},
536
+ )
fleetfinder/views.py CHANGED
@@ -462,9 +462,25 @@ def fleet_details(request, fleet_id):
462
462
  :return:
463
463
  """
464
464
 
465
- context = {"fleet_id": fleet_id}
465
+ try:
466
+ fleet = Fleet.objects.get(fleet_id=fleet_id)
467
+ except Fleet.DoesNotExist:
468
+ logger.debug(f"Fleet with ID {fleet_id} does not exist.")
466
469
 
467
- logger.info(msg=f"Fleet {fleet_id} details view called by {request.user}")
470
+ messages.error(
471
+ request,
472
+ mark_safe(
473
+ _(
474
+ "<h4>Error!</h4><p>Fleet does not exist or is no longer available.</p>"
475
+ )
476
+ ),
477
+ )
478
+
479
+ return redirect("fleetfinder:dashboard")
480
+
481
+ context = {"fleet": fleet}
482
+
483
+ logger.info(msg=f"Fleet {fleet.fleet_id} details view called by {request.user}")
468
484
 
469
485
  return render(
470
486
  request=request,
@@ -485,7 +501,20 @@ def ajax_fleet_details(
485
501
  :param fleet_id:
486
502
  """
487
503
 
488
- fleet = get_fleet_composition(fleet_id)
504
+ try:
505
+ fleet = get_fleet_composition(fleet_id)
506
+ except Fleet.DoesNotExist:
507
+ logger.debug(f"Fleet with ID {fleet_id} does not exist.")
508
+
509
+ return JsonResponse(
510
+ data={"error": _(f"Fleet with ID {fleet_id} does not exist.")}, safe=False
511
+ )
512
+ except RuntimeError as ex:
513
+ logger.debug(f"Error retrieving fleet composition: {ex}", exc_info=True)
514
+
515
+ return JsonResponse(
516
+ data={"error": _(f"Error retrieving fleet composition: {ex}")}, safe=False
517
+ )
489
518
 
490
519
  data = {
491
520
  "fleet_member": list(fleet.fleet),