aa-structures 2.13.0__py3-none-any.whl → 2.14.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: aa-structures
3
- Version: 2.13.0
3
+ Version: 2.14.0
4
4
  Summary: App for managing Eve Online structures with Alliance Auth.
5
5
  Author-email: Erik Kalkoken <kalkoken87@gmail.com>
6
6
  Requires-Python: >=3.8
@@ -1,18 +1,18 @@
1
- structures/__init__.py,sha256=sxMrwmEo1GeZidz442fY62l0Ke7WzyBdTC6IneBHO3U,204
2
- structures/admin.py,sha256=TYWGPou4s-mgspoGaZSiZu2nxBeNzidL_DcJTv7JGJI,40068
3
- structures/app_settings.py,sha256=7I_6VC7YecCxnSgtyNQa9h1Zdwb11qJhrNaBLgyA5m0,6527
1
+ structures/__init__.py,sha256=OXqNqbUO5Qpf5_RjpBoWJz1oygLBj3q-5FymboOnAeQ,204
2
+ structures/admin.py,sha256=cA04JFghU-zIGC168fhZ5Hj_FYAQxlF6TFVDb8sgO4A,40066
3
+ structures/app_settings.py,sha256=aWA2bTcv_vVwIyisx-G7GBSpfM6JDyt2MWcXKtHnjK0,6651
4
4
  structures/apps.py,sha256=MNZH9l3qWCwuS7OGiKGkBVrDzKoOFlqwDdEgyEFzxVA,195
5
5
  structures/auth_hooks.py,sha256=nRbrixFkAE5gphDokB1E8xhH8FY2VtXVwu0XMmSGBAw,1013
6
- structures/constants.py,sha256=R7sC5esaWJayJpTDbug7dTKxkkXDKXQ-U6M9Qb0GH5s,967
6
+ structures/constants.py,sha256=12iIVgFWSNaH1iYBByrsv3OQKCnNxluuckOdrfgzWzA,995
7
7
  structures/forms.py,sha256=kXs-SGIIj-D9xtFCILKe_PMRU4eHl3-HLNWRhaZKmnQ,391
8
8
  structures/helpers.py,sha256=_dw7j7yobpcV70VwWxoQiptk69b3ksvaDlRQKHg8Kmg,2344
9
- structures/managers.py,sha256=ihqcYgsQkDpCejho5l8pthRbJSNhCuWq6UaijPZVG6o,19793
9
+ structures/managers.py,sha256=6zGvmiP1_TZ1uZlPTxNFYgJ77JIjY3uw2PmYeyrJ10E,20063
10
10
  structures/providers.py,sha256=9QYHd8X5HwBhrbgbX3LUrXXV1KGM3hFhcxK6qIJjJtg,317
11
11
  structures/tasks.py,sha256=INZ3soMYHYfxrQsyTO-ozoxbVX6lpMnF3AUo8ybWIoA,9083
12
12
  structures/urls.py,sha256=An5v27AD_NXMNL66cMVtQNci1C_Aqry32rrvDkMpgYo,1467
13
13
  structures/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- structures/core/notification_timers.py,sha256=G-QwpqP9HFv_Y7KsID9bLF-GluEJ1-duSJQN-zqJtM0,14977
15
- structures/core/notification_types.py,sha256=v4hDwgJFqEEXJl9dDxCwTVmQo5yA_Ixjxs_BQEcwNDE,13224
14
+ structures/core/notification_timers.py,sha256=KLjkVrlCCKTWtHJNUfI3U6ppAIZN8vPnIx1zCafRkKQ,16748
15
+ structures/core/notification_types.py,sha256=Ebl4i-mvyGe4luxeNiPmxf7T9bmghe8NU5Giu9pqPCo,13262
16
16
  structures/core/serializers.py,sha256=4W6kA7mhVvc-1OSiTl_w0rSio_HUsjIBm9qbkEr9vCY,20965
17
17
  structures/core/sovereignty.py,sha256=XKgjAFO4BfLpEEUz_hBr2DVDnvWBq9sL9EEQiT4T0n0,730
18
18
  structures/core/starbases.py,sha256=nw7skHYATrnAV-zuPAuxmhM5E4Aqjc_DHSU2TYWgBng,1757
@@ -65,8 +65,8 @@ structures/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
65
65
  structures/models/__init__.py,sha256=Da0-Z4BtsZ_mlnI6XtyvoE0UtTuOA0psGCuVhVPVKrM,916
66
66
  structures/models/eveuniverse.py,sha256=vTIfzZL9guEDuZHQvRdG6pI4zSEdMqfVE-2SkHdqbRo,2213
67
67
  structures/models/notifications.py,sha256=HwQc-ChdP0ULuJgxUym9_CBsCWCmcHT7whlk9tB2vzw,35364
68
- structures/models/owners.py,sha256=8VDRL1fPep0TN6Nv5MJg1NC6_2V6bMJd6f-eda4-PJg,55697
69
- structures/models/structures_1.py,sha256=e1sI-2zMSKcPi2PyYA--fcc8rn-yQwODzlUHKjeWgLI,29778
68
+ structures/models/owners.py,sha256=9LLjOv7QAICAodx6XUEGGuO6RxLY4h6hZs8EWWiobs0,57875
69
+ structures/models/structures_1.py,sha256=zk380uG2DuQ7exO7THbq5TE5Ro2jr6EyVCIxRJXHIJI,29958
70
70
  structures/models/structures_2.py,sha256=g5Pct5jNmZC-n7fnpLs5UyiP9JLrGdI1vjYucHTV_wQ,10371
71
71
  structures/static/structures/css/global.css,sha256=R4LEH9PwLoN77qkqBK7u2y_vzRC3fq8X_zKnAW7yhiA,1165
72
72
  structures/static/structures/css/main.css,sha256=kNgHhC3tVG376h8-6b71MtiERhYZ48cTIno5Kj6ZFGo,303
@@ -134,9 +134,9 @@ structures/static/structures/vendor/datatables/plugins/filterDropDown.min.js,sha
134
134
  structures/static/structures/vendor/datatables/plugins/rowGroup.bootstrap.min.css,sha256=mfb-mieZDwLASMlQTUNXrZQr8OXvQyEDfV3Xz3E7DSU,384
135
135
  structures/static/structures/vendor/datatables/plugins/rowGroup.dataTables.min.css,sha256=mfb-mieZDwLASMlQTUNXrZQr8OXvQyEDfV3Xz3E7DSU,384
136
136
  structures/templates/structures/base.html,sha256=ND0JjP2Pa537FXOrIj3cziHfDbTh0ZNzrLFJ_zpVhw8,337
137
- structures/templates/structures/public.html,sha256=x9IJXBmToPcBZygWfzhvipopP_yWPaS3VJGSKUASDYQ,3233
137
+ structures/templates/structures/public.html,sha256=R3kjhr1MLC5r9r8L3t-1-Nh7-ngKbRiiZNIwgpsGg70,3239
138
138
  structures/templates/structures/statistics.html,sha256=rVI_wPW4VXIO3IoWna8_GoqWCc5ra-6Jc9iekfusS78,1736
139
- structures/templates/structures/structures.html,sha256=87FkTebE2NvzVh9_nkffDo9wOX4tanLu66xpnG5--SI,8322
139
+ structures/templates/structures/structures.html,sha256=WdtBxY3XumtpO4b23Arw8lVqGJf_gHIeD2AAxaKf7cU,8327
140
140
  structures/templates/structures/modals/fitting_assets.html,sha256=0GSpDAk-wgvaZdxM9JzteUYhoPsek5y-L4TdG2q5Sbg,264
141
141
  structures/templates/structures/modals/fitting_gfx.html,sha256=hmdU1Zv09woGKigUugbBRxScKQun8l81yEPUMGrok2g,9530
142
142
  structures/templates/structures/modals/poco_details.html,sha256=uwaEM0jDvkdu_Lff2mUKwm7pn1KXePcgN9yBwFHsQO4,2705
@@ -162,13 +162,13 @@ structures/templatetags/structures.py,sha256=vSt5AnfYuo11q1SxiskT4oshSMsZ9ZgM0nY
162
162
  structures/tests/__init__.py,sha256=9MrJzKr8DdsQY3-79v188pTpwqT4TDQ46vKnBJAGqic,75
163
163
  structures/tests/test_admin.py,sha256=cb_AbOanp31k-oZhaUXCqnlkUu87EgNddq_VQKbB_Dk,26839
164
164
  structures/tests/test_helpers.py,sha256=BQC-4H-9-v5qW4nugqy0bkxuGlA3UO2clYY2lI2LjoY,4893
165
- structures/tests/test_managers_1.py,sha256=ruKq5VRYHdStDQe3uzVhcxF0seQQfmdudJEUYXzi8bY,34539
165
+ structures/tests/test_managers_1.py,sha256=4dnlK7Le04eJUPjJc9ex5BxZWIJGAyEvdKuuYXU2gqY,34846
166
166
  structures/tests/test_managers_2.py,sha256=FISr_NT1Qnu9j1JYYTceNsUk8k9rfCTKwjKi5WUVbPs,3025
167
167
  structures/tests/test_tasks.py,sha256=reL1rPv_kvTgsC4lq5SXulkbEHaJrw5B1-hFI4tiDWY,17269
168
168
  structures/tests/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
169
169
  structures/tests/core/test_notification_structuretimers.py,sha256=OvldjgIHmRVsqoC9C6VJRqwS9WeRFMgpyutRc69PjAU,10857
170
170
  structures/tests/core/test_notification_types.py,sha256=2J5A8Dr9UHhzLEj6QTMty5iZXlNkQxtFmLvU8Wpdno4,10447
171
- structures/tests/core/test_notifications_timerboard.py,sha256=OMkAWH_jWXzCwotp6W2qWG6GpSGisyunZYOoy1UZomU,5583
171
+ structures/tests/core/test_notifications_timers.py,sha256=7Os0xmrfDV2zJeBOFYb7K1KGcwpVHq36IglOdjps6Ac,5915
172
172
  structures/tests/core/test_serializers.py,sha256=ACs6x1IhesoJBFG76RmlglDWqaMlOuqq5kPyzvjGeKI,7056
173
173
  structures/tests/core/test_sovereignty.py,sha256=SGgNw6NxmaU3jV-EDhKJRLfHnRnqDCwm4oCoO-8gt9w,821
174
174
  structures/tests/core/test_starbases.py,sha256=Q3EZVIKZGzxUUvvn0TvZYOL9YQ-baiTRTT83xWqTAhQ,3186
@@ -176,8 +176,8 @@ structures/tests/core/notification_embeds/__init__.py,sha256=47DEQpj8HBSa-_TImW-
176
176
  structures/tests/core/notification_embeds/test_helpers.py,sha256=HG20TC8Bz7NqpaQWo6uvrsxfNIwTL2Zi_NRkh3UyN2Y,1776
177
177
  structures/tests/core/notification_embeds/test_main.py,sha256=rSUm8dGsddjnX_OUwtCh-wd5UhJ6ttTsfxc1soAoDv8,14667
178
178
  structures/tests/integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
179
- structures/tests/integration/test_tasks.py,sha256=s6hL5UBotDmhLq7NUiOad72W-S_18u_yAHiLWVryVi8,23617
180
- structures/tests/integration/test_views.py,sha256=SkbIposTUjo0nujHL4WEaZO7nY1Bfuvm805H39XUv-g,4361
179
+ structures/tests/integration/test_tasks.py,sha256=uhl7TJNELrL1vSDaRee0xEKNMLJuVqRIM32cIVUa_h4,27217
180
+ structures/tests/integration/test_views.py,sha256=qlw1VzlyGW_xnWC-Ii16IHECO-F2x25sYbXeqz2T8IY,4364
181
181
  structures/tests/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
182
182
  structures/tests/models/test_eveuniverse.py,sha256=bQ1upOLfoivfaa9KCrzjWvSH_EvL2STuvfFfZ9saIMY,1623
183
183
  structures/tests/models/test_notifications_1.py,sha256=xVX4an54sZM8WLUdsAGUG2Nb4Rl7W5FBzhI0hBhJo5g,30300
@@ -188,19 +188,19 @@ structures/tests/models/test_owners_1.py,sha256=o8AJasQ0-Pdb0FRFZcmNcMGjaIk8fH3O
188
188
  structures/tests/models/test_owners_2.py,sha256=gkTXpSibPWs1XfZtPvyeEvVebyYZcKERD9GQSYSClYk,21911
189
189
  structures/tests/models/test_owners_3.py,sha256=cpDkfFkVvC1v4uuXha1vGUmWZ163huYTUDuZyed9Rdc,16947
190
190
  structures/tests/models/test_owners_4.py,sha256=_VP4QsPYGwBnn_bVN4aNX2ZVeJzMAzWObZrgCJkOPgY,19496
191
- structures/tests/models/test_owners_5.py,sha256=lTSfVY3rPOLyF-IhlKPT4mbRqvuN_NA-kyU0Vzzp9qY,31607
191
+ structures/tests/models/test_owners_5.py,sha256=TIF_SqJqyGja8FBV2lQNAC4yff1usmJ4e0QW7e68zTg,36929
192
192
  structures/tests/models/test_owners_6.py,sha256=R9UE2QGwiGTWLHzzOZwGQPWJrAPwIIl7UwrHH-ki4Ak,940
193
- structures/tests/models/test_structures.py,sha256=6HktwBOsmRtRq5MnRlZnvNixUsWUpRni_XnPjNpMN0Y,41469
193
+ structures/tests/models/test_structures.py,sha256=iCCa0yYyPHieNfj8TQbdoIv141oLc6hlICZ6AsTnHAw,41849
194
194
  structures/tests/testdata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
195
195
  structures/tests/testdata/create_eveuniverse.py,sha256=6CYgXtJaEwxWvQFyH-P0SKsRqeMuSyJ92ZJiXe_-nuY,2256
196
196
  structures/tests/testdata/entities.json,sha256=vmb0uhwTpD0gycGAakgYidr3qSbnHiMubAd4u7T2l70,36053
197
197
  structures/tests/testdata/esi_data.json,sha256=DX47oSlRZIPxlPueMfiNElbZbAip2cp3LAUmv8GAPJ0,13864
198
198
  structures/tests/testdata/eveuniverse.json,sha256=3fhD72lUNLj96wO4ASbpNcdH_8VbqcXUvKUXR6j0768,1028885
199
- structures/tests/testdata/factories.py,sha256=KNXxOad44DKD0luQ7QS0DepnCr1S_Z6qwfiXnNDy10I,24774
199
+ structures/tests/testdata/factories.py,sha256=UF7Zao2-DjVFLZLEBWaJv4Pg1uw5D6p0TO8ZKuoAI_8,25293
200
200
  structures/tests/testdata/generate_notifications.py,sha256=lhcnbsnE4O6g0OFUAYO2sCfu8vmwBcFtxn-TuTLtFnM,5417
201
201
  structures/tests/testdata/generate_notifications_2.py,sha256=mdoGXd9vgy_opmrwTr9MfGe-QieHV4BHtRA7VFzxFKM,1413
202
202
  structures/tests/testdata/generate_structures.py,sha256=4p2ypDj-goniBnraTP2KfWzEX3YLj0qhKPNTfCXy05g,9134
203
- structures/tests/testdata/helpers.py,sha256=nUecEhe9NqP70zMo8gbAZaWIm2NvzvnF4w9doMtFPY8,6183
203
+ structures/tests/testdata/helpers.py,sha256=FSlg7-6Dn3aMQnQPUnH9o9r8WsV1_BYxElm-E9x_5uo,6316
204
204
  structures/tests/testdata/load_eveuniverse.py,sha256=e3XQNYZXwmhfLQdvO4D1dfbSwhF1Zwr3ag9nG9E3XDM,395
205
205
  structures/tests/testdata/tasks_loadtest.py,sha256=Hn0UVeaMfdi5S4W12lsb6g_xwjQcNYDmeXvQdTsPfBM,963
206
206
  structures/tests/testdata/test_generate_structures.py,sha256=vpReGRROduZsGB99Dq7yPKNKSkSLFdLM_esdf5TTnnI,414
@@ -208,14 +208,14 @@ structures/tests/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
208
208
  structures/tests/views/test_public.py,sha256=FTaT3VUOnoT4t9rCCTCGyDrberpa8CRJHsXuqAw2PQE,1372
209
209
  structures/tests/views/test_service_status.py,sha256=mWkP13HnKRa3ltsZ2FKJuZRJjvYic9wjgQ1J3qA8n-k,4197
210
210
  structures/tests/views/test_statistics.py,sha256=MHt31AIX9_jt8-yP8XsbsiRRUKEtY8mQJbk11saFYZc,2626
211
- structures/tests/views/test_structures.py,sha256=4DrURs32GvbMnXXJm1dC1ZhsDVhJ50iKyiqTRyyRe70,28284
211
+ structures/tests/views/test_structures.py,sha256=KbD3RqUKhWxcRsHfZbgyNHWj7Z9bJt7wOj45s0y17EY,28489
212
212
  structures/tests/views/utils.py,sha256=0ALPjL8d0vcIWa_Pl4_gDl1qtBp71oDqyZLJyvDj-Uc,247
213
213
  structures/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
214
214
  structures/views/common.py,sha256=Z_hcpEpkGrhwFdMAAMON_DqsfQ_lMyRwOMl5ROo_aSk,818
215
215
  structures/views/public.py,sha256=-Tel-poYKZTy8eQllhsWUxVf7HYBuCfeL9JWlbKhs7s,3035
216
216
  structures/views/statistics.py,sha256=7jj8b9ATsYwE7Cg6gMp-bYx29nV43GdWYun9WBggGkM,4709
217
217
  structures/views/status.py,sha256=gcahbk6dPIZDqkaNHDAsEHyDWLzicTK18Fom0A6xx3c,718
218
- structures/views/structures.py,sha256=E1ssnqr2zp0rLQ3txIa6XkcX3NStNjMkuQM4iNjt6yw,22560
218
+ structures/views/structures.py,sha256=Wb57jFfRa0Zxo6TxKmOCQ-jtopzPtHe0go6gnJn40CA,22584
219
219
  structures/webhooks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
220
220
  structures/webhooks/core.py,sha256=mP25MbQG8Yv2YayDFcR6x2V30eqXhR2q5u4SRefblf4,6573
221
221
  structures/webhooks/managers.py,sha256=L3G3AmsyDeif_lfpWshmAxQ61UGJ9w8i9lZaF2jbOtQ,1117
@@ -223,7 +223,7 @@ structures/webhooks/models.py,sha256=kUkt9rnRQIJIrU9Bjcs34rvkb-TMbUubHdn-kny08kI
223
223
  structures/webhooks/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
224
224
  structures/webhooks/tests/test_core.py,sha256=4NcEAQgK2KhQkFOxYh2ad0S-qUWh1DNGDmLo5Mo5opI,6762
225
225
  structures/webhooks/tests/test_utils.py,sha256=ekADFv0JOEtXeqdiejbeqrABO__Q1flJHzVieQ7L9e0,459
226
- aa_structures-2.13.0.dist-info/LICENSE,sha256=XZiwB_S_40_HhnvLg5xvtBb3g1oGjPrk0rpFwk8iInE,1070
227
- aa_structures-2.13.0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
228
- aa_structures-2.13.0.dist-info/METADATA,sha256=nwtm73-vPjR46RQ_UZOVDX0UmhhEou7uOa_2MuQ0A58,5972
229
- aa_structures-2.13.0.dist-info/RECORD,,
226
+ aa_structures-2.14.0.dist-info/LICENSE,sha256=XZiwB_S_40_HhnvLg5xvtBb3g1oGjPrk0rpFwk8iInE,1070
227
+ aa_structures-2.14.0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
228
+ aa_structures-2.14.0.dist-info/METADATA,sha256=X7YMjOqlJc_CkmHeFWJiYgKb8QLDPdnzPibT_bh6ReM,5972
229
+ aa_structures-2.14.0.dist-info/RECORD,,
structures/__init__.py CHANGED
@@ -3,5 +3,5 @@
3
3
  # pylint: disable = invalid-name
4
4
  default_app_config = "structures.apps.StructuresConfig"
5
5
 
6
- __version__ = "2.13.0"
6
+ __version__ = "2.14.0"
7
7
  __title__ = "Structures"
structures/admin.py CHANGED
@@ -324,7 +324,7 @@ class NotificationBaseAdmin(admin.ModelAdmin):
324
324
  message = format_lazy("{first} {second}", first=first, second=second)
325
325
  else:
326
326
  message = first
327
- message = format_lazy({first})
327
+ message = format_lazy(first)
328
328
  self.message_user(request, message)
329
329
 
330
330
 
@@ -49,6 +49,10 @@ STRUCTURES_FEATURE_REFUELED_NOTIFICATIONS = clean_setting(
49
49
  )
50
50
  """Enable / disable refueled notifications feature."""
51
51
 
52
+ STRUCTURES_FEATURE_SKYHOOKS = clean_setting("STRUCTURES_FEATURE_SKYHOOKS", False)
53
+ """Show skyhooks in structures list."""
54
+
55
+
52
56
  STRUCTURES_FEATURE_STARBASES = clean_setting("STRUCTURES_FEATURE_STARBASES", True)
53
57
  """Enable / disable starbases feature."""
54
58
 
structures/constants.py CHANGED
@@ -42,6 +42,7 @@ class EveTypeId(IntEnum):
42
42
  IHUB = 32458
43
43
  JUMP_GATE = 35841
44
44
  LIQUID_OZONE = 16273
45
+ ORBITAL_SKYHOOK = 81080
45
46
  NITROGEN_FUEL_BLOCK = 4051
46
47
  STRONTIUM = 16275
47
48
  TCU = 32226
@@ -53,7 +53,9 @@ def add_or_remove_timer(notif: Notification) -> bool:
53
53
  elif notif.notif_type == NotificationType.SOV_STRUCTURE_REINFORCED:
54
54
  timer_processed = _gen_timer_sov_reinforcements(notif)
55
55
  elif notif.notif_type == NotificationType.ORBITAL_REINFORCED:
56
- timer_processed = _gen_timer_orbital_reinforcements(notif)
56
+ timer_processed = _gen_timer_customs_office_reinforcements(notif)
57
+ elif notif.notif_type == NotificationType.SKYHOOK_LOST_SHIELDS:
58
+ timer_processed = _gen_timer_skyhook_reinforcements(notif)
57
59
  elif notif.notif_type in [
58
60
  NotificationType.MOONMINING_EXTRACTION_STARTED,
59
61
  NotificationType.MOONMINING_EXTRACTION_CANCELLED,
@@ -65,9 +67,7 @@ def add_or_remove_timer(notif: Notification) -> bool:
65
67
  elif notif.notif_type == NotificationType.TOWER_REINFORCED_EXTRA:
66
68
  timer_processed = _gen_timer_tower_reinforcements(notif)
67
69
  else:
68
- raise NotImplementedError(
69
- f"Unsupported notification type for timers: {notif.notif_type}"
70
- )
70
+ raise NotImplementedError(notif.notif_type)
71
71
  if timer_processed:
72
72
  logger.info("%s: Created timer for notification", notif.notification_id)
73
73
  notif.is_timer_added = True
@@ -181,8 +181,8 @@ def _gen_timer_sov_reinforcements(notif: Notification) -> bool:
181
181
  return timer_processed
182
182
 
183
183
 
184
- def _gen_timer_orbital_reinforcements(notif: Notification) -> bool:
185
- """Generate timer for orbital reinforcements."""
184
+ def _gen_timer_customs_office_reinforcements(notif: Notification) -> bool:
185
+ """Generate timer for customs office reinforcements."""
186
186
 
187
187
  solar_system = notif.eve_solar_system()
188
188
  planet = notif.eve_planet()
@@ -231,6 +231,53 @@ def _gen_timer_orbital_reinforcements(notif: Notification) -> bool:
231
231
  return timer_processed
232
232
 
233
233
 
234
+ def _gen_timer_skyhook_reinforcements(notif: Notification) -> bool:
235
+ """Generate timer for skyhook reinforcements."""
236
+
237
+ solar_system = notif.eve_solar_system("solarsystemID")
238
+ structure_type = notif.eve_structure_type("typeID")
239
+ planet = notif.eve_planet()
240
+ eve_time = ldap_time_2_datetime(notif.parsed_text()["timestamp"])
241
+ timer_processed = False
242
+
243
+ if AuthTimer:
244
+ AuthTimer.objects.create(
245
+ details=gettext("Final timer"),
246
+ system=solar_system.name,
247
+ planet_moon=planet.name,
248
+ structure="POCO", # Auth timer does not support the Skyhook type yet
249
+ objective="Friendly",
250
+ eve_time=eve_time,
251
+ eve_corp=notif.owner.corporation,
252
+ corp_timer=STRUCTURES_TIMERS_ARE_CORP_RESTRICTED,
253
+ )
254
+ timer_processed = True
255
+
256
+ if Timer:
257
+ visibility = (
258
+ Timer.Visibility.CORPORATION
259
+ if STRUCTURES_TIMERS_ARE_CORP_RESTRICTED
260
+ else Timer.Visibility.UNRESTRICTED
261
+ )
262
+ Timer.objects.create(
263
+ eve_solar_system=solar_system,
264
+ structure_type=structure_type,
265
+ timer_type=Timer.Type.FINAL,
266
+ objective=Timer.Objective.FRIENDLY,
267
+ date=eve_time,
268
+ location_details=planet.name,
269
+ eve_corporation=notif.owner.corporation,
270
+ eve_alliance=notif.owner.corporation.alliance,
271
+ visibility=visibility,
272
+ structure_name=structure_type.name,
273
+ owner_name=notif.owner.corporation.corporation_name,
274
+ details_notes=_timer_details_notes(notif),
275
+ )
276
+ timer_processed = True
277
+
278
+ return timer_processed
279
+
280
+
234
281
  def _gen_timer_moon_extraction(notif: Notification) -> bool:
235
282
  """Generate timer for moon mining extractions."""
236
283
  solar_system = notif.eve_solar_system()
@@ -221,6 +221,7 @@ class NotificationType(models.TextChoices):
221
221
  cls.STRUCTURE_LOST_ARMOR,
222
222
  cls.STRUCTURE_LOST_SHIELD,
223
223
  cls.TOWER_REINFORCED_EXTRA,
224
+ cls.SKYHOOK_LOST_SHIELDS,
224
225
  }
225
226
 
226
227
  @classmethod
structures/managers.py CHANGED
@@ -2,9 +2,11 @@
2
2
 
3
3
  # pylint: disable=missing-class-docstring
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  import datetime as dt
6
8
  import itertools
7
- from typing import Any, Iterable, Optional, Set, Tuple
9
+ from typing import TYPE_CHECKING, Any, Iterable, Optional, Set, Tuple
8
10
 
9
11
  from django.contrib.auth.models import User
10
12
  from django.db import models, transaction
@@ -24,6 +26,9 @@ from .core.notification_types import NotificationType
24
26
  from .providers import esi
25
27
  from .webhooks.managers import WebhookBaseManager
26
28
 
29
+ if TYPE_CHECKING:
30
+ from .models import Owner
31
+
27
32
  logger = LoggerAddTag(get_extension_logger(__name__), __title__)
28
33
 
29
34
 
@@ -231,6 +236,10 @@ class StructureQuerySet(models.QuerySet):
231
236
  """Filter for starbases."""
232
237
  return self.filter(eve_type__eve_group__eve_category=EveCategoryId.STARBASE)
233
238
 
239
+ def filter_skyhooks(self) -> models.QuerySet:
240
+ """Filter for skyhooks."""
241
+ return self.filter(eve_type=EveTypeId.ORBITAL_SKYHOOK)
242
+
234
243
  def ids(self) -> Set[int]:
235
244
  """Return ids as set."""
236
245
  return set(self.values_list("id", flat=True))
@@ -380,7 +389,9 @@ class StructureManagerBase(models.Manager):
380
389
  obj, created = self.update_or_create_from_dict(structure=structure, owner=owner)
381
390
  return obj, created
382
391
 
383
- def update_or_create_from_dict(self, structure: dict, owner) -> Tuple[Any, bool]:
392
+ def update_or_create_from_dict(
393
+ self, structure: dict, owner: Owner
394
+ ) -> Tuple[Any, bool]:
384
395
  """update or create structure from given dict"""
385
396
 
386
397
  eve_type: EveType = EveType.objects.get_or_create_esi(id=structure["type_id"])[
@@ -37,6 +37,7 @@ from structures.app_settings import (
37
37
  STRUCTURES_DEVELOPER_MODE,
38
38
  STRUCTURES_ESI_DIRECTOR_ERROR_MAX_RETRIES,
39
39
  STRUCTURES_FEATURE_CUSTOMS_OFFICES,
40
+ STRUCTURES_FEATURE_SKYHOOKS,
40
41
  STRUCTURES_FEATURE_STARBASES,
41
42
  STRUCTURES_HOURS_UNTIL_STALE_NOTIFICATION,
42
43
  STRUCTURES_NOTIFICATION_SYNC_GRACE_MINUTES,
@@ -545,14 +546,14 @@ class Owner(models.Model):
545
546
  )
546
547
 
547
548
  def _remove_structures_not_returned_from_esi(
548
- self, structures_qs: models.QuerySet, new_structures: Iterable
549
+ self, existing_structures: models.QuerySet, new_structures: Iterable[dict]
549
550
  ):
550
551
  """Remove structures no longer returned from ESI."""
551
- ids_local = {x.id for x in structures_qs}
552
+ ids_local = {x.id for x in existing_structures}
552
553
  ids_from_esi = {x["structure_id"] for x in new_structures}
553
554
  ids_to_remove = ids_local - ids_from_esi
554
555
  if len(ids_to_remove) > 0:
555
- structures_qs.filter(id__in=ids_to_remove).delete()
556
+ existing_structures.filter(id__in=ids_to_remove).delete()
556
557
  logger.info(
557
558
  "Removed %d structures which apparently no longer exist.",
558
559
  len(ids_to_remove),
@@ -650,7 +651,7 @@ class Owner(models.Model):
650
651
  self._store_raw_data("structures", structures)
651
652
 
652
653
  self._remove_structures_not_returned_from_esi(
653
- structures_qs=self.structures.filter_upwell_structures(),
654
+ existing_structures=self.structures.filter_upwell_structures(),
654
655
  new_structures=structures,
655
656
  )
656
657
  return is_ok
@@ -701,7 +702,7 @@ class Owner(models.Model):
701
702
  return False
702
703
 
703
704
  self._remove_structures_not_returned_from_esi(
704
- structures_qs=self.structures.filter_customs_offices(),
705
+ existing_structures=self.structures.filter_customs_offices(),
705
706
  new_structures=structures.values(),
706
707
  )
707
708
  return True
@@ -868,7 +869,7 @@ class Owner(models.Model):
868
869
  return False
869
870
 
870
871
  self._remove_structures_not_returned_from_esi(
871
- structures_qs=self.structures.filter_starbases(),
872
+ existing_structures=self.structures.filter_starbases(),
872
873
  new_structures=structures,
873
874
  )
874
875
  character.reset_error_counter()
@@ -1214,15 +1215,18 @@ class Owner(models.Model):
1214
1215
  def update_asset_esi(self, user: Optional[User] = None):
1215
1216
  """Update assets from ESI."""
1216
1217
  token = self.fetch_token()
1217
- assets_data = self._fetch_structure_assets_from_esi(token)
1218
+ assets_data = self._fetch_owner_assets_from_esi(token)
1218
1219
  self._store_items_for_upwell_structures(assets_data)
1219
1220
  self._store_items_for_starbases(assets_data)
1221
+ if STRUCTURES_FEATURE_SKYHOOKS:
1222
+ self._update_skyhooks_from_assets(assets_data)
1223
+ self._resolve_skyhook_planets()
1220
1224
  if user:
1221
1225
  self._send_report_to_user(
1222
1226
  topic="assets", topic_count=self.structures.count(), user=user
1223
1227
  )
1224
1228
 
1225
- def _fetch_structure_assets_from_esi(self, token: Token) -> dict:
1229
+ def _fetch_owner_assets_from_esi(self, token: Token) -> dict:
1226
1230
  assets_raw = esi.client.Assets.get_corporations_corporation_id_assets(
1227
1231
  corporation_id=self.corporation.corporation_id,
1228
1232
  token=token.valid_access_token(),
@@ -1306,6 +1310,62 @@ class Owner(models.Model):
1306
1310
  )
1307
1311
  structure.update_items(structure_items)
1308
1312
 
1313
+ def _update_skyhooks_from_assets(self, assets_data: dict):
1314
+ skyhooks = {
1315
+ item_id: item
1316
+ for item_id, item in assets_data.items()
1317
+ if item["type_id"] == EveTypeId.ORBITAL_SKYHOOK
1318
+ and item["location_type"] == "solar_system"
1319
+ and item["location_flag"] == "AutoFit"
1320
+ and item["is_singleton"]
1321
+ and item["position"]
1322
+ }
1323
+ structures = []
1324
+ for item in skyhooks.values():
1325
+ structures.append(
1326
+ {
1327
+ "corporation_id": self.corporation.corporation_id,
1328
+ "type_id": item["type_id"],
1329
+ "position": item["position"],
1330
+ "structure_id": item["item_id"],
1331
+ "system_id": item["location_id"],
1332
+ }
1333
+ )
1334
+
1335
+ for s in structures:
1336
+ Structure.objects.update_or_create_from_dict(s, self)
1337
+
1338
+ self._remove_structures_not_returned_from_esi(
1339
+ existing_structures=self.structures.filter_skyhooks(),
1340
+ new_structures=structures,
1341
+ )
1342
+
1343
+ def _resolve_skyhook_planets(self):
1344
+ """Add planets to all unresolved Skyhooks."""
1345
+ s: Structure
1346
+ for s in self.structures.filter_skyhooks().filter(
1347
+ eve_planet__isnull=True,
1348
+ position_x__isnull=False,
1349
+ position_y__isnull=False,
1350
+ position_z__isnull=False,
1351
+ ):
1352
+ try:
1353
+ celestial = s.eve_solar_system.nearest_celestial(
1354
+ x=s.position_x,
1355
+ y=s.position_y,
1356
+ z=s.position_z,
1357
+ group_id=EveGroupId.PLANET,
1358
+ )
1359
+ except OSError:
1360
+ continue
1361
+
1362
+ if not celestial or not isinstance(celestial.eve_object, EvePlanet):
1363
+ continue
1364
+
1365
+ s.eve_planet = celestial.eve_object
1366
+ s.name = celestial.eve_type.name
1367
+ s.save()
1368
+
1309
1369
  @staticmethod
1310
1370
  def get_esi_scopes() -> List[str]:
1311
1371
  """Return all required ESI scopes."""
@@ -464,6 +464,11 @@ class Structure(models.Model): # pylint: disable = too-many-public-methods
464
464
  """Return True if this structure is a starbase, else False."""
465
465
  return starbases.is_starbase(self.eve_type)
466
466
 
467
+ @property
468
+ def is_skyhook(self) -> bool:
469
+ """Return True if this structure is a skyhook, else False."""
470
+ return self.eve_type_id == EveTypeId.ORBITAL_SKYHOOK
471
+
467
472
  @cached_property
468
473
  def is_upwell_structure(self) -> bool:
469
474
  """Return True if this structure is an upwell structure, else False."""
@@ -28,7 +28,7 @@
28
28
  <ul id="public-tabs" class="nav nav-tabs" role="tablist">
29
29
 
30
30
  <li role="presentation" class="active">
31
- <a href="#pocos" aria-controls="pocos" role="tab" data-toggle="tab">
31
+ <a href="#pocos" aria-controls="orbitals" role="tab" data-toggle="tab">
32
32
  {% translate "Customs Offices" %} <small>({{ pocos_count|default:"-" }})</small>
33
33
  </a>
34
34
  </li>
@@ -39,7 +39,7 @@
39
39
  <div class="panel-body">
40
40
  <div class="tab-content">
41
41
 
42
- <div role="tabpanel" class="tab-pane active" id="pocos">
42
+ <div role="tabpanel" class="tab-pane active" id="orbitals">
43
43
  {% include "structures/partials/public/poco_list.html" %}
44
44
  </div>
45
45
 
@@ -48,8 +48,8 @@
48
48
  </li>
49
49
 
50
50
  <li role="presentation">
51
- <a href="#pocos" aria-controls="pocos" role="tab" data-toggle="tab">
52
- {% trans "Customs Offices" %} <small>({{ pocos_count|default:"-" }})</small>
51
+ <a href="#orbitals" aria-controls="orbitals" role="tab" data-toggle="tab">
52
+ {% trans "Orbitals" %} <small>({{ orbitals_count|default:"-" }})</small>
53
53
  </a>
54
54
  </li>
55
55
 
@@ -77,7 +77,7 @@
77
77
  {% include "structures/partials/structures/structure_list.html" %}
78
78
  </div>
79
79
 
80
- <div role="tabpanel" class="tab-pane" id="pocos">
80
+ <div role="tabpanel" class="tab-pane" id="orbitals">
81
81
  {% include "structures/partials/structures/poco_list.html" %}
82
82
  </div>
83
83
 
@@ -4,6 +4,7 @@ from app_utils.django import app_labels
4
4
  from app_utils.testing import NoSocketsTestCase
5
5
 
6
6
  from structures.core import notification_timers
7
+ from structures.core.notification_types import NotificationType
7
8
  from structures.models import Notification
8
9
  from structures.tests.testdata.factories import (
9
10
  GeneratedNotificationFactory,
@@ -107,7 +108,11 @@ if "timerboard" in app_labels():
107
108
  @patch(MODULE_PATH + ".STRUCTURES_MOON_EXTRACTION_TIMERS_ENABLED", True)
108
109
  def test_run_all(self):
109
110
  for obj in Notification.objects.all():
110
- obj.add_or_remove_timer()
111
+ timer_types = NotificationType.relevant_for_timerboard()
112
+ with self.subTest(notif_type=obj.notif_type):
113
+ is_timer = obj.notif_type in timer_types
114
+ is_added = obj.add_or_remove_timer()
115
+ self.assertEqual(is_timer, is_added)
111
116
 
112
117
  @patch(MODULE_PATH + ".STRUCTURES_TIMERS_ARE_CORP_RESTRICTED", False)
113
118
  def test_corp_restriction_1(self):
@@ -7,7 +7,7 @@ import yaml
7
7
 
8
8
  from django.test import TestCase, override_settings
9
9
  from django.utils.timezone import now
10
- from eveuniverse.models import EveSolarSystem
10
+ from eveuniverse.models import EvePlanet, EveSolarSystem
11
11
 
12
12
  from app_utils.django import app_labels
13
13
  from app_utils.esi import EsiStatus
@@ -22,11 +22,13 @@ from structures.tests.testdata.factories import (
22
22
  NotificationFactory,
23
23
  OwnerFactory,
24
24
  RawNotificationFactory,
25
+ SkyhookFactory,
25
26
  StarbaseFactory,
26
27
  StructureFactory,
27
28
  WebhookFactory,
28
29
  datetime_to_esi,
29
30
  )
31
+ from structures.tests.testdata.helpers import NearestCelestial
30
32
  from structures.tests.testdata.load_eveuniverse import load_eveuniverse
31
33
 
32
34
  if "structuretimers" in app_labels():
@@ -613,3 +615,102 @@ class TestTasks(TestCase):
613
615
  self.assertEqual(len(embeds), 1)
614
616
  embed = embeds[0]
615
617
  self.assertIn("Territorial Claim Unit", embed.title)
618
+
619
+ @patch(OWNERS_PATH + ".STRUCTURES_FEATURE_SKYHOOKS", True)
620
+ def test_should_fetch_new_skyhooks_from_esi(
621
+ self, mock_esi_2, mock_esi, mock_execute
622
+ ):
623
+ # given
624
+ owner = OwnerFactory()
625
+ structure = SkyhookFactory(owner=owner)
626
+ eve_planet = EvePlanet.objects.get(id=40161469)
627
+ corporation_id = owner.corporation.corporation_id
628
+ endpoints = [
629
+ EsiEndpoint(
630
+ "Assets",
631
+ "get_corporations_corporation_id_assets",
632
+ "corporation_id",
633
+ needs_token=True,
634
+ data={
635
+ str(corporation_id): [
636
+ {
637
+ "is_singleton": True,
638
+ "item_id": structure.id,
639
+ "location_flag": "AutoFit",
640
+ "location_id": 30002537,
641
+ "location_type": "solar_system",
642
+ "quantity": 1,
643
+ "type_id": 81080,
644
+ },
645
+ ]
646
+ },
647
+ ),
648
+ EsiEndpoint(
649
+ "Assets",
650
+ "post_corporations_corporation_id_assets_names",
651
+ "corporation_id",
652
+ needs_token=True,
653
+ data={str(corporation_id): []},
654
+ ),
655
+ EsiEndpoint(
656
+ "Assets",
657
+ "post_corporations_corporation_id_assets_locations",
658
+ "corporation_id",
659
+ needs_token=True,
660
+ data={
661
+ str(corporation_id): [
662
+ {"item_id": structure.id, "position": {"x": 1, "y": 2, "z": 3}}
663
+ ]
664
+ },
665
+ ),
666
+ EsiEndpoint(
667
+ "Corporation",
668
+ "get_corporations_corporation_id_starbases",
669
+ "corporation_id",
670
+ needs_token=True,
671
+ data={str(corporation_id): []},
672
+ ),
673
+ EsiEndpoint(
674
+ "Corporation",
675
+ "get_corporations_corporation_id_structures",
676
+ "corporation_id",
677
+ needs_token=True,
678
+ data={str(corporation_id): []},
679
+ ),
680
+ EsiEndpoint(
681
+ "Planetary_Interaction",
682
+ "get_corporations_corporation_id_customs_offices",
683
+ "corporation_id",
684
+ needs_token=True,
685
+ data={str(corporation_id): []},
686
+ ),
687
+ EsiEndpoint(
688
+ "Sovereignty",
689
+ "get_sovereignty_map",
690
+ needs_token=False,
691
+ data=[],
692
+ ),
693
+ EsiEndpoint(
694
+ "Universe",
695
+ "get_universe_structures_structure_id",
696
+ "structure_id",
697
+ needs_token=True,
698
+ data={},
699
+ ),
700
+ ]
701
+ mock_esi.client = mock_esi_2.client = EsiClientStub.create_from_endpoints(
702
+ endpoints
703
+ )
704
+ structure_id = structure.id
705
+ structure.delete()
706
+ # when
707
+
708
+ with patch(OWNERS_PATH + ".EveSolarSystem.nearest_celestial") as m:
709
+ m.return_value = NearestCelestial(
710
+ eve_object=eve_planet,
711
+ distance=35_000_000,
712
+ eve_type=eve_planet.eve_type,
713
+ )
714
+ tasks.update_all_structures.delay()
715
+ # then
716
+ self.assertTrue(owner.structures.filter(id=structure_id).exists())
@@ -46,7 +46,7 @@ class TestStructureListView(TestCase):
46
46
  # then
47
47
  self.assertEqual(response.status_code, 200)
48
48
  self.assertEqual(response.context["structures_count"], 2)
49
- self.assertEqual(response.context["pocos_count"], 1)
49
+ self.assertEqual(response.context["orbitals_count"], 1)
50
50
  self.assertEqual(response.context["starbases_count"], 1)
51
51
  self.assertEqual(response.context["jump_gates_count"], 1)
52
52
  self.assertIn("data_export", response.context)
@@ -7,18 +7,20 @@ from pytz import utc
7
7
  from django.test import override_settings
8
8
  from django.utils.timezone import now
9
9
  from esi.models import Token
10
+ from eveuniverse.models import EvePlanet
10
11
 
11
12
  from app_utils.esi_testing import EsiClientStub, EsiEndpoint
12
13
  from app_utils.testing import BravadoResponseStub, NoSocketsTestCase, queryset_pks
13
14
 
14
15
  from structures.core.notification_types import NotificationType
15
- from structures.models import Notification, StructureItem
16
+ from structures.models import Notification, Structure, StructureItem
16
17
  from structures.tests.testdata.factories import (
17
18
  EveCharacterFactory,
18
19
  EveCorporationInfoFactory,
19
20
  EveEntityCorporationFactory,
20
21
  JumpFuelAlertConfigFactory,
21
22
  OwnerFactory,
23
+ SkyhookFactory,
22
24
  StarbaseFactory,
23
25
  StructureFactory,
24
26
  StructureItemFactory,
@@ -27,6 +29,7 @@ from structures.tests.testdata.factories import (
27
29
  datetime_to_esi,
28
30
  )
29
31
  from structures.tests.testdata.helpers import (
32
+ NearestCelestial,
30
33
  load_eve_entities,
31
34
  load_notification_entities,
32
35
  )
@@ -711,6 +714,137 @@ class TestOwnerUpdateAssetEsi(NoSocketsTestCase):
711
714
  self.assertTrue(structure.items.filter(id=1300000003001).exists())
712
715
 
713
716
 
717
+ @patch(OWNERS_PATH + ".STRUCTURES_FEATURE_SKYHOOKS", True)
718
+ @patch(OWNERS_PATH + ".EveSolarSystem.nearest_celestial")
719
+ @patch(OWNERS_PATH + ".esi")
720
+ class TestOwnerUpdateSkyhooks(NoSocketsTestCase):
721
+ @classmethod
722
+ def setUpClass(cls):
723
+ super().setUpClass()
724
+ load_eveuniverse()
725
+ cls.corporation = EveCharacterFactory()
726
+ character = EveCharacterFactory(corporation=cls.corporation)
727
+ cls.user = UserMainDefaultOwnerFactory(main_character__character=character)
728
+ cls.planet = EvePlanet.objects.get(id=40161469)
729
+ endpoints = [
730
+ EsiEndpoint(
731
+ "Assets",
732
+ "get_corporations_corporation_id_assets",
733
+ "corporation_id",
734
+ needs_token=True,
735
+ data={
736
+ f"{cls.corporation.corporation_id}": [
737
+ {
738
+ "is_singleton": True,
739
+ "item_id": 1000000010001,
740
+ "location_flag": "AutoFit",
741
+ "location_id": 30002537,
742
+ "location_type": "solar_system",
743
+ "quantity": 1,
744
+ "type_id": 81080,
745
+ },
746
+ ],
747
+ },
748
+ ),
749
+ EsiEndpoint(
750
+ "Assets",
751
+ "post_corporations_corporation_id_assets_locations",
752
+ "corporation_id",
753
+ needs_token=True,
754
+ data={
755
+ f"{cls.corporation.corporation_id}": [
756
+ {"item_id": 1000000010001, "position": {"x": 1, "y": 2, "z": 3}}
757
+ ]
758
+ },
759
+ ),
760
+ ]
761
+ cls.esi_client_stub = EsiClientStub.create_from_endpoints(endpoints)
762
+
763
+ def test_should_create_new_skyhooks_from_scratch(
764
+ self, mock_esi, mock_nearest_celestial
765
+ ):
766
+ # given
767
+ mock_esi.client = self.esi_client_stub
768
+ mock_nearest_celestial.return_value = NearestCelestial(
769
+ eve_object=self.planet, distance=35_000_000, eve_type=self.planet.eve_type
770
+ )
771
+ owner = OwnerFactory(user=self.user, assets_last_update_at=None)
772
+ # when
773
+ owner.update_asset_esi()
774
+ # then
775
+ owner.refresh_from_db()
776
+ self.assertEqual(owner.structures.count(), 1)
777
+ obj: Structure = owner.structures.get(pk=1000000010001)
778
+ self.assertTrue(obj.is_skyhook)
779
+ self.assertEqual(obj.eve_planet, self.planet)
780
+
781
+ def test_should_remove_obsolete_skyhooks(self, mock_esi, mock_nearest_celestial):
782
+ # given
783
+ mock_esi.client = self.esi_client_stub
784
+ mock_nearest_celestial.return_value = NearestCelestial(
785
+ eve_object=self.planet, distance=35_000_000, eve_type=self.planet.eve_type
786
+ )
787
+ owner = OwnerFactory(user=self.user, assets_last_update_at=None)
788
+ SkyhookFactory.create(owner=owner)
789
+ # when
790
+ owner.update_asset_esi()
791
+ # then
792
+ owner.refresh_from_db()
793
+ self.assertEqual(owner.structures.count(), 1)
794
+ obj: Structure = owner.structures.get(pk=1000000010001)
795
+ self.assertTrue(obj.is_skyhook)
796
+ self.assertEqual(obj.eve_planet, self.planet)
797
+
798
+ def test_should_update_existing_skyhook(self, mock_esi, mock_nearest_celestial):
799
+ # given
800
+ mock_esi.client = self.esi_client_stub
801
+ mock_nearest_celestial.return_value = NearestCelestial(
802
+ eve_object=self.planet, distance=35_000_000, eve_type=self.planet.eve_type
803
+ )
804
+ owner = OwnerFactory(user=self.user, assets_last_update_at=None)
805
+ SkyhookFactory.create(owner=owner, id=1000000010001, eve_planet_name="Thera I")
806
+ # when
807
+ owner.update_asset_esi()
808
+ # then
809
+ owner.refresh_from_db()
810
+ self.assertEqual(owner.structures.count(), 1)
811
+ obj: Structure = owner.structures.get(pk=1000000010001)
812
+ self.assertTrue(obj.is_skyhook)
813
+ self.assertEqual(obj.eve_planet, self.planet)
814
+
815
+ def test_should_ignore_os_error_when_resolving_planet(
816
+ self, mock_esi, mock_nearest_celestial
817
+ ):
818
+ # given
819
+ mock_esi.client = self.esi_client_stub
820
+ mock_nearest_celestial.side_effect = OSError
821
+ owner = OwnerFactory(user=self.user, assets_last_update_at=None)
822
+ # when
823
+ owner.update_asset_esi()
824
+ # then
825
+ owner.refresh_from_db()
826
+ self.assertEqual(owner.structures.count(), 1)
827
+ obj: Structure = owner.structures.get(pk=1000000010001)
828
+ self.assertTrue(obj.is_skyhook)
829
+ self.assertIsNone(obj.eve_planet)
830
+
831
+ def test_should_ignore_no_reply_when_resolving_planet(
832
+ self, mock_esi, mock_nearest_celestial
833
+ ):
834
+ # given
835
+ mock_esi.client = self.esi_client_stub
836
+ mock_nearest_celestial.return_value = None
837
+ owner = OwnerFactory(user=self.user, assets_last_update_at=None)
838
+ # when
839
+ owner.update_asset_esi()
840
+ # then
841
+ owner.refresh_from_db()
842
+ self.assertEqual(owner.structures.count(), 1)
843
+ obj: Structure = owner.structures.get(pk=1000000010001)
844
+ self.assertTrue(obj.is_skyhook)
845
+ self.assertIsNone(obj.eve_planet)
846
+
847
+
714
848
  class TestOwnerToken(NoSocketsTestCase):
715
849
  def test_should_return_valid_token(self):
716
850
  # given
@@ -29,6 +29,7 @@ from structures.tests.testdata.factories import (
29
29
  OwnerFactory,
30
30
  PocoDetailsFactory,
31
31
  PocoFactory,
32
+ SkyhookFactory,
32
33
  StarbaseFactory,
33
34
  StructureFactory,
34
35
  StructureItemFactory,
@@ -367,48 +368,6 @@ class TestStructure(NoSocketsTestCase):
367
368
  # when/then
368
369
  self.assertEqual(obj.location_name, "?")
369
370
 
370
- def test_is_poco(self):
371
- # given
372
- structure = StructureFactory.build(owner=self.owner)
373
- poco = PocoFactory.build(owner=self.owner)
374
- starbase = StarbaseFactory.build(owner=self.owner)
375
- # then
376
- self.assertFalse(structure.is_poco)
377
- self.assertTrue(poco.is_poco)
378
- self.assertFalse(starbase.is_poco)
379
-
380
- def test_is_starbase(self):
381
- # given
382
- structure = StructureFactory.build(owner=self.owner)
383
- poco = PocoFactory.build(owner=self.owner)
384
- starbase = StarbaseFactory.build(owner=self.owner)
385
- # then
386
- self.assertFalse(structure.is_starbase)
387
- self.assertFalse(poco.is_starbase)
388
- self.assertTrue(starbase.is_starbase)
389
-
390
- def test_is_upwell_structure(self):
391
- # given
392
- structure = StructureFactory.build(owner=self.owner)
393
- poco = PocoFactory.build(owner=self.owner)
394
- starbase = StarbaseFactory.build(owner=self.owner)
395
- # then
396
- self.assertTrue(structure.is_upwell_structure)
397
- self.assertFalse(poco.is_upwell_structure)
398
- self.assertFalse(starbase.is_upwell_structure)
399
-
400
- def test_is_jump_gate(self):
401
- # given
402
- normal_structure = StructureFactory.build(owner=self.owner)
403
- poco = PocoFactory.build(owner=self.owner)
404
- starbase = StarbaseFactory.build(owner=self.owner)
405
- jump_gate = JumpGateFactory.build(owner=self.owner)
406
- # then
407
- self.assertFalse(normal_structure.is_jump_gate)
408
- self.assertFalse(poco.is_jump_gate)
409
- self.assertFalse(starbase.is_jump_gate)
410
- self.assertTrue(jump_gate.is_jump_gate)
411
-
412
371
  # TODO: activate
413
372
  # def test_is_upwell_structure_data_error(self):
414
373
  # # group without a category
@@ -490,6 +449,54 @@ class TestStructure(NoSocketsTestCase):
490
449
  self.assertFalse(obj.owner_has_sov())
491
450
 
492
451
 
452
+ class TestStructureIsX(NoSocketsTestCase):
453
+ @classmethod
454
+ def setUpClass(cls):
455
+ super().setUpClass()
456
+ load_eveuniverse()
457
+ cls.owner = OwnerFactory()
458
+ cls.jump_gate = JumpGateFactory.build(owner=cls.owner)
459
+ cls.poco = PocoFactory.build(owner=cls.owner)
460
+ cls.skyhook = SkyhookFactory.build(owner=cls.owner)
461
+ cls.starbase = StarbaseFactory.build(owner=cls.owner)
462
+ cls.upwell_structure = StructureFactory.build(owner=cls.owner)
463
+
464
+ def test_is_jump_gate(self):
465
+ self.assertFalse(self.upwell_structure.is_jump_gate)
466
+ self.assertFalse(self.poco.is_jump_gate)
467
+ self.assertFalse(self.starbase.is_jump_gate)
468
+ self.assertTrue(self.jump_gate.is_jump_gate)
469
+ self.assertFalse(self.skyhook.is_jump_gate)
470
+
471
+ def test_is_poco(self):
472
+ self.assertFalse(self.upwell_structure.is_poco)
473
+ self.assertTrue(self.poco.is_poco)
474
+ self.assertFalse(self.starbase.is_poco)
475
+ self.assertFalse(self.jump_gate.is_poco)
476
+ self.assertFalse(self.skyhook.is_poco)
477
+
478
+ def test_is_starbase(self):
479
+ self.assertFalse(self.upwell_structure.is_starbase)
480
+ self.assertFalse(self.poco.is_starbase)
481
+ self.assertTrue(self.starbase.is_starbase)
482
+ self.assertFalse(self.jump_gate.is_starbase)
483
+ self.assertFalse(self.skyhook.is_starbase)
484
+
485
+ def test_is_skyhook(self):
486
+ self.assertFalse(self.upwell_structure.is_skyhook)
487
+ self.assertFalse(self.poco.is_skyhook)
488
+ self.assertFalse(self.starbase.is_skyhook)
489
+ self.assertFalse(self.jump_gate.is_skyhook)
490
+ self.assertTrue(self.skyhook.is_skyhook)
491
+
492
+ def test_is_upwell_structure(self):
493
+ self.assertTrue(self.upwell_structure.is_upwell_structure)
494
+ self.assertFalse(self.poco.is_upwell_structure)
495
+ self.assertFalse(self.starbase.is_upwell_structure)
496
+ self.assertTrue(self.jump_gate.is_upwell_structure)
497
+ self.assertFalse(self.skyhook.is_upwell_structure)
498
+
499
+
493
500
  class TestStructureFuel(NoSocketsTestCase):
494
501
  @classmethod
495
502
  def setUpClass(cls):
@@ -25,6 +25,7 @@ from .testdata.factories import (
25
25
  EveSovereigntyMapFactory,
26
26
  OwnerFactory,
27
27
  PocoFactory,
28
+ SkyhookFactory,
28
29
  StarbaseFactory,
29
30
  StructureFactory,
30
31
  StructureTagFactory,
@@ -304,12 +305,15 @@ class TestStructureQuerySet(NoSocketsTestCase):
304
305
  cls.structure = StructureFactory(owner=cls.owner)
305
306
  cls.poco = PocoFactory(owner=cls.owner)
306
307
  cls.starbase = StarbaseFactory(owner=cls.owner)
308
+ cls.skyhook = SkyhookFactory(owner=cls.owner)
307
309
 
308
310
  def test_should_return_ids_as_set(self):
309
311
  # when
310
312
  ids = Structure.objects.ids()
311
313
  # then
312
- self.assertSetEqual(ids, {self.structure.id, self.poco.id, self.starbase.id})
314
+ self.assertSetEqual(
315
+ ids, {self.structure.id, self.poco.id, self.starbase.id, self.skyhook.id}
316
+ )
313
317
 
314
318
  def test_should_filter_upwell_structures(self):
315
319
  # when
@@ -329,6 +333,12 @@ class TestStructureQuerySet(NoSocketsTestCase):
329
333
  # then
330
334
  self.assertSetEqual(result_qs.ids(), {self.starbase.id})
331
335
 
336
+ def test_should_filter_skyhooks(self):
337
+ # when
338
+ result_qs = Structure.objects.filter_skyhooks()
339
+ # then
340
+ self.assertSetEqual(result_qs.ids(), {self.skyhook.id})
341
+
332
342
 
333
343
  class TestStructureQuerySetVisibleForUser(NoSocketsTestCase):
334
344
  @classmethod
@@ -476,6 +476,27 @@ class PocoDetailsFactory(
476
476
  standing_level = PocoDetails.StandingLevel.BAD
477
477
 
478
478
 
479
+ class SkyhookFactory(StructureFactory):
480
+ class Params:
481
+ eve_planet_name = "Amamake V"
482
+
483
+ has_fitting = None
484
+ has_core = None
485
+ state = Structure.State.NA
486
+
487
+ @factory.lazy_attribute
488
+ def eve_planet(self):
489
+ return EvePlanet.objects.get(name=self.eve_planet_name)
490
+
491
+ @factory.lazy_attribute
492
+ def eve_solar_system(self):
493
+ return self.eve_planet.eve_solar_system
494
+
495
+ @factory.lazy_attribute
496
+ def eve_type(self):
497
+ return EveType.objects.get(id=EveTypeId.ORBITAL_SKYHOOK)
498
+
499
+
479
500
  class JumpGateFactory(StructureFactory):
480
501
  @factory.lazy_attribute
481
502
  def eve_type(self):
@@ -4,6 +4,7 @@ import datetime as dt
4
4
  import json
5
5
  import logging
6
6
  import unicodedata
7
+ from collections import namedtuple
7
8
  from pathlib import Path
8
9
  from random import randrange
9
10
 
@@ -192,3 +193,8 @@ def datetime_to_ldap(my_dt: dt.datetime) -> int:
192
193
  ((my_dt - dt.datetime(1970, 1, 1, tzinfo=pytz.utc)).total_seconds())
193
194
  + 11644473600
194
195
  ) * 10000000
196
+
197
+
198
+ NearestCelestial = namedtuple(
199
+ "NearestCelestial", ["eve_type", "eve_object", "distance"]
200
+ )
@@ -19,6 +19,7 @@ from structures.tests.testdata.factories import (
19
19
  JumpGateFactory,
20
20
  OwnerFactory,
21
21
  PocoFactory,
22
+ SkyhookFactory,
22
23
  StarbaseFactory,
23
24
  StructureFactory,
24
25
  StructureTagFactory,
@@ -148,6 +149,7 @@ class TestStructureListDataFilterVariant(TestCase):
148
149
  owner = OwnerFactory(user=cls.user)
149
150
  cls.structure = StructureFactory(owner=owner)
150
151
  cls.poco = PocoFactory(owner=owner)
152
+ cls.skyhook = SkyhookFactory(owner=owner)
151
153
  cls.starbase = StarbaseFactory(owner=owner)
152
154
  cls.jump_gate = JumpGateFactory(owner=owner)
153
155
 
@@ -163,17 +165,17 @@ class TestStructureListDataFilterVariant(TestCase):
163
165
  structure_ids = set(data.keys())
164
166
  self.assertSetEqual(structure_ids, {self.structure.id, self.jump_gate.id})
165
167
 
166
- def test_should_return_pocos_only(self):
168
+ def test_should_return_orbitals_only(self):
167
169
  # given
168
170
  request = self.factory.get("/")
169
171
  request.user = self.user
170
172
  # when
171
- response = structures.structure_list_data(request, "pocos")
173
+ response = structures.structure_list_data(request, "orbitals")
172
174
  # then
173
175
  self.assertEqual(response.status_code, 200)
174
176
  data = json_response_to_dict(response)
175
177
  structure_ids = set(data.keys())
176
- self.assertSetEqual(structure_ids, {self.poco.id})
178
+ self.assertSetEqual(structure_ids, {self.poco.id, self.skyhook.id})
177
179
 
178
180
  def test_should_return_starbases_only(self):
179
181
  # given
@@ -211,7 +213,13 @@ class TestStructureListDataFilterVariant(TestCase):
211
213
  structure_ids = set(data.keys())
212
214
  self.assertSetEqual(
213
215
  structure_ids,
214
- {self.structure.id, self.poco.id, self.starbase.id, self.jump_gate.id},
216
+ {
217
+ self.structure.id,
218
+ self.poco.id,
219
+ self.starbase.id,
220
+ self.jump_gate.id,
221
+ self.skyhook.id,
222
+ },
215
223
  )
216
224
 
217
225
  def test_should_raise_error_when_invalid_variant_requested(self):
@@ -58,7 +58,7 @@ class StructureSelection(str, Enum):
58
58
  """A pre-defined selection to filter structures data."""
59
59
 
60
60
  STRUCTURES = "structures"
61
- POCOS = "pocos"
61
+ ORBITALS = "orbitals"
62
62
  STARBASES = "starbases"
63
63
  JUMP_GATES = "jump_gates"
64
64
  ALL = "all"
@@ -118,8 +118,8 @@ def structure_list(request: HttpRequest):
118
118
  structures_count = _structures_query(
119
119
  request.user, StructureSelection.STRUCTURES, tags
120
120
  ).count()
121
- pocos_count = _structures_query(
122
- request.user, StructureSelection.POCOS, tags
121
+ orbitals_count = _structures_query(
122
+ request.user, StructureSelection.ORBITALS, tags
123
123
  ).count()
124
124
  starbases_count = _structures_query(
125
125
  request.user, StructureSelection.STARBASES, tags
@@ -136,7 +136,7 @@ def structure_list(request: HttpRequest):
136
136
  "tags_exist": StructureTag.objects.exists(),
137
137
  "show_jump_gates_tab": STRUCTURES_SHOW_JUMP_GATES,
138
138
  "structures_count": structures_count,
139
- "pocos_count": pocos_count,
139
+ "orbitals_count": orbitals_count,
140
140
  "starbases_count": starbases_count,
141
141
  "jump_gates_count": jump_gates_count,
142
142
  "data_export": data_export,
@@ -146,7 +146,7 @@ def structure_list(request: HttpRequest):
146
146
 
147
147
  def _construct_data_export(request, tags):
148
148
  structures_ajax_url = _construct_ajax_url(StructureSelection.STRUCTURES, tags)
149
- pocos_ajax_url = _construct_ajax_url(StructureSelection.POCOS, tags)
149
+ pocos_ajax_url = _construct_ajax_url(StructureSelection.ORBITALS, tags)
150
150
  starbases_ajax_url = _construct_ajax_url(StructureSelection.STARBASES, tags)
151
151
  jump_gates_ajax_url = _construct_ajax_url(StructureSelection.JUMP_GATES, tags)
152
152
 
@@ -213,7 +213,7 @@ def _structures_query(
213
213
  eve_type__eve_group__eve_category_id=EveCategoryId.STRUCTURE
214
214
  )
215
215
 
216
- elif selection == StructureSelection.POCOS:
216
+ elif selection == StructureSelection.ORBITALS:
217
217
  structures_qs = structures_qs.filter(
218
218
  eve_type__eve_group__eve_category_id=EveCategoryId.ORBITAL
219
219
  ).annotate_has_poco_details()