aa-fleetfinder 2.6.1__py3-none-any.whl → 2.7.0__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.6.1.dist-info → aa_fleetfinder-2.7.0.dist-info}/METADATA +1 -1
  2. {aa_fleetfinder-2.6.1.dist-info → aa_fleetfinder-2.7.0.dist-info}/RECORD +38 -33
  3. fleetfinder/__init__.py +1 -1
  4. fleetfinder/locale/cs_CZ/LC_MESSAGES/django.po +82 -18
  5. fleetfinder/locale/de/LC_MESSAGES/django.mo +0 -0
  6. fleetfinder/locale/de/LC_MESSAGES/django.po +94 -19
  7. fleetfinder/locale/django.pot +84 -19
  8. fleetfinder/locale/es/LC_MESSAGES/django.po +93 -18
  9. fleetfinder/locale/fr_FR/LC_MESSAGES/django.po +86 -18
  10. fleetfinder/locale/it_IT/LC_MESSAGES/django.po +79 -18
  11. fleetfinder/locale/ja/LC_MESSAGES/django.po +83 -18
  12. fleetfinder/locale/ko_KR/LC_MESSAGES/django.po +93 -18
  13. fleetfinder/locale/nl_NL/LC_MESSAGES/django.po +79 -18
  14. fleetfinder/locale/pl_PL/LC_MESSAGES/django.po +81 -18
  15. fleetfinder/locale/ru/LC_MESSAGES/django.mo +0 -0
  16. fleetfinder/locale/ru/LC_MESSAGES/django.po +97 -22
  17. fleetfinder/locale/sk/LC_MESSAGES/django.po +79 -18
  18. fleetfinder/locale/uk/LC_MESSAGES/django.mo +0 -0
  19. fleetfinder/locale/uk/LC_MESSAGES/django.po +97 -22
  20. fleetfinder/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
  21. fleetfinder/locale/zh_Hans/LC_MESSAGES/django.po +97 -22
  22. fleetfinder/static/fleetfinder/css/fleetfinder.css +21 -0
  23. fleetfinder/static/fleetfinder/css/fleetfinder.min.css +1 -1
  24. fleetfinder/static/fleetfinder/css/fleetfinder.min.css.map +1 -1
  25. fleetfinder/static/fleetfinder/js/fleetfinder.js +23 -0
  26. fleetfinder/static/fleetfinder/js/fleetfinder.min.js +2 -0
  27. fleetfinder/static/fleetfinder/js/fleetfinder.min.js.map +1 -0
  28. fleetfinder/tasks.py +21 -5
  29. fleetfinder/templates/fleetfinder/bundles/js/fleetfinder-js.html +3 -0
  30. fleetfinder/templates/fleetfinder/dashboard.html +38 -63
  31. fleetfinder/templates/fleetfinder/fleet-details.html +124 -33
  32. fleetfinder/templates/fleetfinder/join-fleet.html +11 -1
  33. fleetfinder/templates/fleetfinder/modals/kick-fleet-member.html +46 -0
  34. fleetfinder/tests/test_views.py +67 -10
  35. fleetfinder/urls.py +5 -0
  36. fleetfinder/views.py +141 -42
  37. {aa_fleetfinder-2.6.1.dist-info → aa_fleetfinder-2.7.0.dist-info}/WHEEL +0 -0
  38. {aa_fleetfinder-2.6.1.dist-info → aa_fleetfinder-2.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -19,12 +19,7 @@
19
19
  <th>{% translate "Fleet commander" %}</th>
20
20
  <th>{% translate "Fleet name" %}</th>
21
21
  <th>{% translate "Created at" %}</th>
22
- <th>{% translate "Join" %}</th>
23
-
24
- {% if perms.fleetfinder.manage_fleets %}
25
- <th>{% translate "Details" %}</th>
26
- <th>{% translate "Edit" %}</th>
27
- {% endif %}
22
+ <th class="text-end"><!-- {% translate "Join" %} --></th>
28
23
  </tr>
29
24
  </thead>
30
25
 
@@ -43,20 +38,33 @@
43
38
  {% block extra_javascript %}
44
39
  {% include "bundles/datatables-js-bs5.html" %}
45
40
  {% include "bundles/moment-js.html" with locale=True %}
41
+ {% include "fleetfinder/bundles/js/fleetfinder-js.html" %}
46
42
 
47
43
  {% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %}
48
44
 
49
45
  <script>
46
+ /* global fetchGet, fleetfinderBootstrapTooltip, DataTable */
47
+ 'use strict';
48
+
50
49
  $(document).ready(() => {
51
50
  const DATETIME_FORMAT = 'YYYY-MM-DD, HH:mm';
52
51
  const table_fleet_overview = $('#table_available-fleets');
52
+ let dataTable = null;
53
53
 
54
54
  /**
55
- * Initialize the datatable for fleet overview
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.
56
60
  */
57
- fetchGet({url: '{% url "fleetfinder:ajax_dashboard" %}'})
58
- .then((data) => {
59
- table_fleet_overview.DataTable({
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({
60
68
  language: {
61
69
  url: '{{ DT_LANG_PATH }}'
62
70
  },
@@ -69,15 +77,16 @@
69
77
  sort: 'sort'
70
78
  }
71
79
  },
72
- {data: 'fleet_name'},
80
+ {
81
+ data: 'fleet_name'
82
+ },
73
83
  {
74
84
  data: 'created_at',
75
85
  },
76
- {data: 'join'},
77
- {% if perms.fleetfinder.manage_fleets %}
78
- {data: 'details'},
79
- {data: 'edit'},
80
- {% endif %}
86
+ {
87
+ data: 'actions',
88
+ className: 'text-end',
89
+ },
81
90
  ],
82
91
  columnDefs: [
83
92
  {
@@ -88,66 +97,32 @@
88
97
  orderable: false,
89
98
  targets: [3]
90
99
  },
91
- {% if perms.fleetfinder.manage_fleets %}
92
- {
93
- orderable: false,
94
- targets: [4, 5]
95
- },
96
- {% endif %}
97
100
  ],
98
101
  order: [[0, 'asc']],
99
102
  paging: false
100
103
  });
101
- })
102
- .catch((error) => {
103
- console.error('Error fetching fleet data:', error);
104
- });
104
+ }
105
105
 
106
- /**
107
- * Refresh the datatable information every 30 seconds
108
- */
109
- const intervalReloadDatatable = 30000; // ms
110
- let expectedReloadDatatable = Date.now() + intervalReloadDatatable;
106
+ // Initialize Bootstrap tooltips
107
+ fleetfinderBootstrapTooltip({selector: '#table_available-fleets'});
108
+ };
111
109
 
112
110
  /**
113
- * reload datatable "fleetfinderFleetsTable"
111
+ * Fetch and update fleet data
114
112
  */
115
- const reloadDataTable = () => {
116
- const dt = Date.now() - expectedReloadDatatable; // The drift (positive for overshooting)
117
-
118
- if (dt > intervalReloadDatatable) {
119
- // Something awful happened. Maybe the browser (tab) was inactive?
120
- // Possibly special handling to avoid futile "catch up" run
121
- console.log('Something went wrong, reloading page ...');
122
-
123
- window.location.replace(
124
- window.location.pathname + window.location.search + window.location.hash
125
- );
126
- }
127
-
113
+ const fetchFleetData = () => {
128
114
  fetchGet({url: '{% url "fleetfinder:ajax_dashboard" %}'})
129
- .then((newData) => {
130
- const dataTable = table_fleet_overview.DataTable();
131
-
132
- dataTable.clear().rows.add(newData).draw();
133
- })
115
+ .then(initializeOrUpdateTable)
134
116
  .catch((error) => {
135
- console.error('Error fetching updated data:', error);
117
+ console.error('Error fetching fleet data:', error);
136
118
  });
137
-
138
- expectedReloadDatatable += intervalReloadDatatable;
139
-
140
- // Take drift into account
141
- setTimeout(
142
- reloadDataTable,
143
- Math.max(0, intervalReloadDatatable - dt)
144
- );
145
119
  };
146
120
 
147
- setTimeout(
148
- reloadDataTable,
149
- intervalReloadDatatable
150
- );
121
+ // Initial load
122
+ fetchFleetData();
123
+
124
+ // Refresh every 30 seconds
125
+ setInterval(fetchFleetData, 30000);
151
126
  });
152
127
  </script>
153
128
  {% endblock %}
@@ -50,6 +50,7 @@
50
50
  <th>{% translate "Name" %}</th>
51
51
  <th>{% translate "Ship class" %}</th>
52
52
  <th>{% translate "System" %}</th>
53
+ <th><!-- {% translate "Action" %} --></th>
53
54
  </tr>
54
55
  </thead>
55
56
 
@@ -60,6 +61,8 @@
60
61
  </div>
61
62
  </div>
62
63
  </div>
64
+
65
+ {% include "fleetfinder/modals/kick-fleet-member.html" %}
63
66
  {% endblock %}
64
67
 
65
68
  {% block extra_css %}
@@ -69,39 +72,83 @@
69
72
 
70
73
  {% block extra_javascript %}
71
74
  {% include "bundles/datatables-js-bs5.html" %}
75
+ {% include "fleetfinder/bundles/js/fleetfinder-js.html" %}
72
76
 
73
77
  {% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %}
74
78
 
79
+ {% translate "Fleet boss" as l10nFleetBoss %}
80
+ {% translate "Kick member from fleet" as l10nKickMemberFromFleet %}
81
+ {% translate "An unknown error occurred." as l10nUnknownError %}
82
+
75
83
  <script>
76
84
  $(document).ready(() => {
85
+ /* DataTables
86
+ ------------------------------------------------------------------------- */
77
87
  const table_fleet_members = $('#table_fleet_members');
78
- const tabe_fleet_composition = $('#table_fleet_composition');
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
+ };
79
100
 
80
101
  const populateDatatables = () => {
81
102
  fetchGet({
82
- url: '{% url "fleetfinder:ajax_fleet_details" fleet_id %}'
103
+ url: url.fleetDetails,
83
104
  })
84
105
  .then((data) => {
85
106
  table_fleet_members.DataTable({
86
- language: {
87
- url: '{{ DT_LANG_PATH }}'
88
- },
89
- destroy: true,
90
- paging: false,
107
+ ...dataTableConfig,
91
108
  data: data.fleet_member,
92
109
  columns: [
93
- {data: 'character_name'},
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
+ },
94
119
  {data: 'ship_type_name'},
95
- {data: 'solar_system_name'}
96
- ]
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
+ }
97
148
  });
98
149
 
99
- tabe_fleet_composition.DataTable({
100
- language: {
101
- url: '{{ DT_LANG_PATH }}'
102
- },
103
- destroy: true,
104
- paging: false,
150
+ table_fleet_composition.DataTable({
151
+ ...dataTableConfig,
105
152
  data: data.fleet_composition,
106
153
  columns: [
107
154
  {data: 'ship_type_name'},
@@ -110,30 +157,74 @@
110
157
  order: [[1, 'desc']]
111
158
  });
112
159
  })
160
+ .then(() => {
161
+ // Initialize tooltips for the kick buttons
162
+ fleetfinderBootstrapTooltip({selector: '#table_fleet_members'});
163
+ })
113
164
  .catch((error) => {
114
165
  console.error('Error fetching fleet details:', error);
115
166
  });
116
167
  };
117
168
 
118
- table_fleet_members.DataTable({
119
- language: {
120
- url: '{{ DT_LANG_PATH }}'
121
- },
122
- paging: false,
123
- });
124
-
125
- tabe_fleet_composition.DataTable({
126
- language: {
127
- url: '{{ DT_LANG_PATH }}'
128
- },
129
- paging: false,
130
- });
131
-
132
169
  populateDatatables();
133
170
 
134
- setInterval(function () {
135
- populateDatatables();
136
- }, 30000);
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
+ });
137
228
  });
138
229
  </script>
139
230
  {% endblock %}
@@ -18,11 +18,21 @@
18
18
  <div class="card-body container">
19
19
  <div class="row">
20
20
  <div class="align-self-center">
21
+ <p>
22
+ {% translate "Please ensure you don't have any CSPA charges set on your characters, as this will prevent fleet invites from being sent." %}
23
+ <br>
24
+ {% translate "You can select multiple characters to invite them all at once." %}
25
+ </p>
26
+
27
+ <p>
28
+ {% translate "The selected characters will receive a fleet invite in-game, which they can accept to join the fleet." %}
29
+ </p>
30
+
21
31
  <form class="form-signin" role="form" action="" method="POST">
22
32
  {% csrf_token %}
23
33
 
24
34
  <div class="mb-3">
25
- <label for="character_ids">{% translate "Select the characters to invite" %}</label>
35
+ <label for="character_ids" class="mb-1">{% translate "Please select the characters you want to invite:" %}</label>
26
36
 
27
37
  <select class="multiselect-ui" multiple="multiple" name="character_ids" id="character_ids">
28
38
  {% for character in characters %}
@@ -0,0 +1,46 @@
1
+ {% load bootstrap %}
2
+ {% load i18n %}
3
+
4
+ <!-- kick fleet member modal -->
5
+ <div class="modal fade" id="kick-fleet-member" tabindex="-1" role="dialog" aria-hidden="true">
6
+ <div class="modal-dialog modal-dialog-centered" role="document">
7
+ <div class="modal-content">
8
+ <div class="modal-header">
9
+ <div class="modal-title fs-5">
10
+ {% translate "Kick member from fleet" %}
11
+ </div>
12
+
13
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% translate 'Close' %}"></button>
14
+ </div>
15
+
16
+ <div class="modal-body">
17
+ <p>
18
+ {% translate "Are you sure you want to kick this member from the fleet?" %}
19
+ </p>
20
+
21
+ <p>
22
+ <strong id="kick-fleet-member-character-name"></strong>
23
+ </p>
24
+
25
+ <p class="aa-callout aa-callout-danger modal-kick-fleet-member-error d-none">
26
+ <i class="fa-solid fa-triangle-exclamation"></i>
27
+ {% translate "Error" %}
28
+ <br>
29
+ <span class="modal-kick-fleet-member-error-message"></span>
30
+ </p>
31
+ </div>
32
+
33
+ <div class="modal-footer">
34
+ <button id="modal-button-cancel-kick-fleet-member" type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">
35
+ <i class="fa-regular fa-hand"></i>
36
+ {% translate "Cancel" %}
37
+ </button>
38
+
39
+ <button id="modal-button-confirm-kick-fleet-member" type="button" class="btn btn-success btn-sm">
40
+ <i class="fa-solid fa-check"></i>
41
+ {% translate "Confirm" %}
42
+ </button>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ </div>
@@ -101,14 +101,14 @@ class TestAjaxDashboardView(FleetfinderTestViews):
101
101
  It should also filter fleets based on the user's groups.
102
102
  """
103
103
 
104
- # python
105
104
  @patch("fleetfinder.views.get_all_characters_from_user")
106
- def test_renders_dashboard_with_fleet_data(self, mock_get_characters):
105
+ def test_renders_dashboard_with_fleet_data_with_basic_access(
106
+ self, mock_get_characters
107
+ ):
107
108
  """
108
- Test that the ajax_dashboard view renders the dashboard with fleet data.
109
+ Test that the ajax_dashboard view renders the dashboard with fleet data
110
+ when the user has basic access permissions.
109
111
 
110
- :param mock_portrait_url:
111
- :type mock_portrait_url:
112
112
  :param mock_get_characters:
113
113
  :type mock_get_characters:
114
114
  :return:
@@ -124,9 +124,55 @@ class TestAjaxDashboardView(FleetfinderTestViews):
124
124
 
125
125
  self.client.force_login(self.user_with_basic_acces_perms)
126
126
  url = reverse("fleetfinder:ajax_dashboard")
127
+ join_url = reverse("fleetfinder:join_fleet", args=[self.fleet_id])
128
+ response = self.client.get(url)
129
+
130
+ expected_response = [
131
+ {
132
+ "fleet_commander": {
133
+ "html": '<img class="rounded eve-character-portrait" src="https://images.evetech.net/characters/1000/portrait?size=32" alt="Jean Luc Picard" loading="lazy">Jean Luc Picard',
134
+ "sort": "Jean Luc Picard",
135
+ },
136
+ "fleet_name": "Starfleet",
137
+ "created_at": dt_to_iso(self.fleet_created_at),
138
+ "actions": f'<a href="{join_url}" class="btn btn-sm btn-success ms-1" data-bs-tooltip="aa-fleetfinder" title="Join fleet"><i class="fa-solid fa-right-to-bracket"></i></a>',
139
+ }
140
+ ]
141
+
142
+ self.assertEqual(response.status_code, HTTPStatus.OK)
143
+ self.assertIn("Starfleet", response.json()[0]["fleet_name"])
144
+ self.assertIn("Jean Luc Picard", response.json()[0]["fleet_commander"]["html"])
145
+ self.assertEqual(response.json(), expected_response)
146
+
147
+ @patch("fleetfinder.views.get_all_characters_from_user")
148
+ def test_renders_dashboard_with_fleet_data_with_manage_access(
149
+ self, mock_get_characters
150
+ ):
151
+ """
152
+ Test that the ajax_dashboard view renders the dashboard with fleet data
153
+ when the user has manage access permissions.
154
+
155
+ :param mock_get_characters:
156
+ :type mock_get_characters:
157
+ :return:
158
+ :rtype:
159
+ """
160
+
161
+ mock_get_characters.return_value = [
162
+ self.user_with_manage_perms.profile.main_character
163
+ ]
164
+
165
+ fleet = self.fleet
166
+ fleet.groups.set([])
167
+
168
+ self.client.force_login(self.user_with_manage_perms)
169
+ url = reverse("fleetfinder:ajax_dashboard")
170
+ join_url = reverse("fleetfinder:join_fleet", args=[self.fleet_id])
171
+ details_url = reverse("fleetfinder:fleet_details", args=[self.fleet_id])
172
+ edit_url = reverse("fleetfinder:edit_fleet", args=[self.fleet_id])
127
173
  response = self.client.get(url)
128
174
 
129
- expected_resonse = [
175
+ expected_response = [
130
176
  {
131
177
  "fleet_commander": {
132
178
  "html": '<img class="rounded eve-character-portrait" src="https://images.evetech.net/characters/1000/portrait?size=32" alt="Jean Luc Picard" loading="lazy">Jean Luc Picard',
@@ -134,16 +180,18 @@ class TestAjaxDashboardView(FleetfinderTestViews):
134
180
  },
135
181
  "fleet_name": "Starfleet",
136
182
  "created_at": dt_to_iso(self.fleet_created_at),
137
- "join": '<a href="/fleetfinder/fleet/12345/join/" class="btn btn-sm btn-primary">Join fleet</a>',
138
- "details": "",
139
- "edit": "",
183
+ "actions": (
184
+ f'<a href="{join_url}" class="btn btn-sm btn-success ms-1" data-bs-tooltip="aa-fleetfinder" title="Join fleet"><i class="fa-solid fa-right-to-bracket"></i></a>'
185
+ f'<a href="{details_url}" class="btn btn-sm btn-info ms-1" data-bs-tooltip="aa-fleetfinder" title="View fleet details"><i class="fa-solid fa-eye"></i></a>'
186
+ f'<a href="{edit_url}" class="btn btn-sm btn-warning ms-1" data-bs-tooltip="aa-fleetfinder" title="Edit fleet advert"><i class="fa-solid fa-pen-to-square"></i></a>'
187
+ ),
140
188
  }
141
189
  ]
142
190
 
143
191
  self.assertEqual(response.status_code, HTTPStatus.OK)
144
192
  self.assertIn("Starfleet", response.json()[0]["fleet_name"])
145
193
  self.assertIn("Jean Luc Picard", response.json()[0]["fleet_commander"]["html"])
146
- self.assertEqual(response.json(), expected_resonse)
194
+ self.assertEqual(response.json(), expected_response)
147
195
 
148
196
  @patch("fleetfinder.views.get_all_characters_from_user")
149
197
  def test_returns_empty_data_when_no_fleets_available(self, mock_get_characters):
@@ -171,6 +219,15 @@ class TestAjaxDashboardView(FleetfinderTestViews):
171
219
 
172
220
  @patch("fleetfinder.views.get_all_characters_from_user")
173
221
  def test_filters_fleets_by_user_groups(self, mock_get_characters):
222
+ """
223
+ Test that the ajax_dashboard view filters fleets based on the user's groups.
224
+
225
+ :param mock_get_characters:
226
+ :type mock_get_characters:
227
+ :return:
228
+ :rtype:
229
+ """
230
+
174
231
  mock_get_characters.return_value = [
175
232
  self.user_with_manage_perms.profile.main_character
176
233
  ]
fleetfinder/urls.py CHANGED
@@ -34,6 +34,11 @@ urlpatterns = [
34
34
  view=views.ajax_fleet_details,
35
35
  name="ajax_fleet_details",
36
36
  ),
37
+ path(
38
+ route="fleet/<int:fleet_id>/member/kick/",
39
+ view=views.ajax_fleet_kick_member,
40
+ name="ajax_fleet_kick_member",
41
+ ),
37
42
  ]
38
43
  ),
39
44
  ),