aa-killtracker 0.12.0__py3-none-any.whl → 0.12.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.
@@ -1,17 +1,12 @@
1
1
  """Tracker models for killtracker."""
2
2
 
3
- import json
4
3
  from datetime import timedelta
5
4
  from typing import List, Optional, Tuple
6
5
 
7
- import dhooks_lite
8
- from simple_mq import SimpleMQ
9
-
10
6
  from django.contrib.auth.models import Group, User
11
- from django.core.cache import cache
12
7
  from django.db import models
8
+ from django.db.models import Q
13
9
  from django.utils.timezone import now
14
- from django.utils.translation import gettext_lazy as _
15
10
  from eveuniverse.helpers import meters_to_ly
16
11
  from eveuniverse.models import (
17
12
  EveConstellation,
@@ -24,251 +19,83 @@ from eveuniverse.models import (
24
19
  from allianceauth.authentication.models import State
25
20
  from allianceauth.eveonline.models import EveAllianceInfo, EveCorporationInfo
26
21
  from allianceauth.services.hooks import get_extension_logger
27
- from app_utils.allianceauth import get_redis_client
28
- from app_utils.json import JSONDateTimeDecoder, JSONDateTimeEncoder
29
22
  from app_utils.logging import LoggerAddTag
30
- from app_utils.urls import static_file_absolute_url
31
23
 
32
- from killtracker import APP_NAME, HOMEPAGE_URL, __title__, __version__
33
- from killtracker.app_settings import (
34
- KILLTRACKER_KILLMAIL_MAX_AGE_FOR_TRACKER,
35
- KILLTRACKER_WEBHOOK_SET_AVATAR,
36
- )
24
+ from killtracker import __title__
25
+ from killtracker.app_settings import KILLTRACKER_KILLMAIL_MAX_AGE_FOR_TRACKER
26
+ from killtracker.constants import EveCategoryId, EveGroupId
37
27
  from killtracker.core.killmails import Killmail
38
- from killtracker.exceptions import WebhookTooManyRequests
39
- from killtracker.managers import EveTypePlusManager, TrackerManager, WebhookManager
40
-
41
- logger = LoggerAddTag(get_extension_logger(__name__), __title__)
42
-
43
-
44
- class EveTypePlus(EveType):
45
- """Variant to show group names with default output."""
28
+ from killtracker.managers import TrackerManager
46
29
 
47
- class Meta:
48
- proxy = True
30
+ from .webhooks import Webhook
49
31
 
50
- objects = EveTypePlusManager()
51
-
52
- def __str__(self) -> str:
53
- return f"{self.name} ({self.eve_group})"
54
-
55
-
56
- class Webhook(models.Model):
57
- """A webhook to receive messages"""
32
+ logger = LoggerAddTag(get_extension_logger(__name__), __title__)
58
33
 
59
- HTTP_TOO_MANY_REQUESTS = 429
60
34
 
61
- class WebhookType(models.IntegerChoices):
62
- """A webhook type."""
35
+ def _require_attackers_ship_groups_query():
36
+ return Q(
37
+ eve_category_id__in=[
38
+ EveCategoryId.STRUCTURE,
39
+ EveCategoryId.SHIP,
40
+ EveCategoryId.FIGHTER,
41
+ ],
42
+ published=True,
43
+ ) | Q(eve_category_id=EveCategoryId.ENTITY)
63
44
 
64
- DISCORD = 1, _("Discord Webhook")
65
45
 
66
- name = models.CharField(
67
- max_length=64, unique=True, help_text="short name to identify this webhook"
68
- )
69
- webhook_type = models.IntegerField(
70
- choices=WebhookType.choices,
71
- default=WebhookType.DISCORD,
72
- help_text="type of this webhook",
73
- )
74
- url = models.CharField(
75
- max_length=255,
76
- unique=True,
77
- help_text=(
78
- "URL of this webhook, e.g. "
79
- "https://discordapp.com/api/webhooks/123456/abcdef"
80
- ),
81
- )
82
- notes = models.TextField(
83
- blank=True,
84
- help_text="you can add notes about this webhook here if you want",
85
- )
86
- is_enabled = models.BooleanField(
87
- default=True,
88
- db_index=True,
89
- help_text="whether notifications are currently sent to this webhook",
46
+ def _require_attackers_ship_types_query():
47
+ return Q(
48
+ eve_group__eve_category_id__in=[
49
+ EveCategoryId.STRUCTURE,
50
+ EveCategoryId.SHIP,
51
+ EveCategoryId.FIGHTER,
52
+ ],
53
+ published=True,
54
+ ) | Q(
55
+ eve_group__eve_category_id=EveCategoryId.ENTITY,
56
+ mass__gt=1,
57
+ volume__gt=1,
90
58
  )
91
- objects = WebhookManager()
92
59
 
93
- def __init__(self, *args, **kwargs) -> None:
94
- super().__init__(*args, **kwargs)
95
- self.main_queue = self._create_queue("main")
96
- self.error_queue = self._create_queue("error")
97
60
 
98
- def __str__(self) -> str:
99
- return self.name
61
+ def _require_attackers_weapon_groups_query():
62
+ return Q(id__in=EveGroupId.weapons())
100
63
 
101
- def __repr__(self) -> str:
102
- return f"{self.__class__.__name__}(id={self.id}, name='{self.name}')" # type: ignore
103
-
104
- def __getstate__(self):
105
- # Copy the object's state from self.__dict__ which contains
106
- # all our instance attributes. Always use the dict.copy()
107
- # method to avoid modifying the original state.
108
- state = self.__dict__.copy()
109
- # Remove the unpicklable entries.
110
- del state["main_queue"]
111
- del state["error_queue"]
112
- return state
113
-
114
- def __setstate__(self, state):
115
- # Restore instance attributes (i.e., filename and lineno).
116
- self.__dict__.update(state)
117
- # Restore the previously opened file's state. To do so, we need to
118
- # reopen it and read from it until the line count is restored.
119
- self.main_queue = self._create_queue("main")
120
- self.error_queue = self._create_queue("error")
121
64
 
122
- def save(self, *args, **kwargs):
123
- is_new = self.id is None # type: ignore
124
- super().save(*args, **kwargs)
125
- if is_new:
126
- self.main_queue = self._create_queue("main")
127
- self.error_queue = self._create_queue("error")
65
+ def _require_attackers_weapon_types_query():
66
+ return Q(eve_group__in=EveGroupId.weapons())
128
67
 
129
- def _create_queue(self, suffix: str) -> Optional[SimpleMQ]:
130
- redis_client = get_redis_client()
131
- return (
132
- SimpleMQ(redis_client, f"{__title__}_webhook_{self.pk}_{suffix}")
133
- if self.pk
134
- else None
135
- )
136
68
 
137
- def reset_failed_messages(self) -> int:
138
- """moves all messages from error queue into main queue.
139
- returns number of moved messages.
140
- """
141
- counter = 0
142
- if self.error_queue and self.main_queue:
143
- while True:
144
- message = self.error_queue.dequeue()
145
- if message is None:
146
- break
147
-
148
- self.main_queue.enqueue(message)
149
- counter += 1
150
-
151
- return counter
152
-
153
- def enqueue_message(
154
- self,
155
- content: Optional[str] = None,
156
- embeds: Optional[List[dhooks_lite.Embed]] = None,
157
- tts: Optional[bool] = None,
158
- username: Optional[str] = None,
159
- avatar_url: Optional[str] = None,
160
- ) -> int:
161
- """Enqueues a message to be send with this webhook"""
162
- if not self.main_queue:
163
- return 0
164
-
165
- username = __title__ if KILLTRACKER_WEBHOOK_SET_AVATAR else username
166
- brand_url = static_file_absolute_url("killtracker/killtracker_logo.png")
167
- avatar_url = brand_url if KILLTRACKER_WEBHOOK_SET_AVATAR else avatar_url
168
- return self.main_queue.enqueue(
169
- self._discord_message_asjson(
170
- content=content,
171
- embeds=embeds,
172
- tts=tts,
173
- username=username,
174
- avatar_url=avatar_url,
175
- )
69
+ def _require_victim_ship_groups_query():
70
+ return (
71
+ Q(
72
+ eve_category_id__in=[
73
+ EveCategoryId.STRUCTURE,
74
+ EveCategoryId.SHIP,
75
+ EveCategoryId.FIGHTER,
76
+ EveCategoryId.DEPLOYABLE,
77
+ ],
78
+ published=True,
176
79
  )
80
+ | Q(id=EveGroupId.MINING_DRONE, published=True)
81
+ | Q(id=EveGroupId.ORBITAL_INFRASTRUCTURE)
82
+ )
177
83
 
178
- @staticmethod
179
- def _discord_message_asjson(
180
- content: Optional[str] = None,
181
- embeds: Optional[List[dhooks_lite.Embed]] = None,
182
- tts: Optional[bool] = None,
183
- username: Optional[str] = None,
184
- avatar_url: Optional[str] = None,
185
- ) -> str:
186
- """Converts a Discord message to JSON and returns it
187
-
188
- Raises ValueError if message is incomplete
189
- """
190
- if not content and not embeds:
191
- raise ValueError("Message must have content or embeds to be valid")
192
-
193
- if embeds:
194
- embeds_list = [obj.asdict() for obj in embeds]
195
- else:
196
- embeds_list = None
197
-
198
- message = {}
199
- if content:
200
- message["content"] = content
201
- if embeds_list:
202
- message["embeds"] = embeds_list
203
- if tts:
204
- message["tts"] = tts
205
- if username:
206
- message["username"] = username
207
- if avatar_url:
208
- message["avatar_url"] = avatar_url
209
-
210
- return json.dumps(message, cls=JSONDateTimeEncoder)
211
-
212
- def send_message_to_webhook(self, message_json: str) -> dhooks_lite.WebhookResponse:
213
- """Send given message to webhook
214
-
215
- Params
216
- message_json: Discord message encoded in JSON
217
- """
218
- timeout = cache.ttl(self._blocked_cache_key()) # type: ignore
219
- if timeout:
220
- raise WebhookTooManyRequests(timeout)
221
-
222
- message = json.loads(message_json, cls=JSONDateTimeDecoder)
223
- if message.get("embeds"):
224
- embeds = [
225
- dhooks_lite.Embed.from_dict(embed_dict)
226
- for embed_dict in message.get("embeds")
227
- ]
228
- else:
229
- embeds = None
230
- hook = dhooks_lite.Webhook(
231
- url=self.url,
232
- user_agent=dhooks_lite.UserAgent(
233
- name=APP_NAME, url=HOMEPAGE_URL, version=__version__
234
- ),
235
- )
236
- response = hook.execute(
237
- content=message.get("content"),
238
- embeds=embeds,
239
- username=message.get("username"),
240
- avatar_url=message.get("avatar_url"),
241
- wait_for_response=True,
242
- max_retries=0, # we will handle retries ourselves
243
- )
244
- logger.debug("headers: %s", response.headers)
245
- logger.debug("status_code: %s", response.status_code)
246
- logger.debug("content: %s", response.content)
247
- if response.status_code == self.HTTP_TOO_MANY_REQUESTS:
248
- logger.error(
249
- "%s: Received too many requests error from API: %s",
250
- self,
251
- response.content,
252
- )
253
- try:
254
- retry_after = int(response.headers["Retry-After"]) + 2
255
- except (ValueError, KeyError):
256
- retry_after = WebhookTooManyRequests.DEFAULT_RESET_AFTER
257
- cache.set(
258
- key=self._blocked_cache_key(), value="BLOCKED", timeout=retry_after
259
- )
260
- raise WebhookTooManyRequests(retry_after)
261
- return response
262
-
263
- def _blocked_cache_key(self) -> str:
264
- return f"{__title__}_webhook_{self.pk}_blocked"
265
84
 
266
- @staticmethod
267
- def create_message_link(name: str, url: str) -> str:
268
- """Create link for a Discord message"""
269
- if name and url:
270
- return f"[{str(name)}]({str(url)})"
271
- return str(name)
85
+ def _require_victim_ship_types_query():
86
+ return (
87
+ Q(
88
+ eve_group__eve_category_id__in=[
89
+ EveCategoryId.STRUCTURE,
90
+ EveCategoryId.SHIP,
91
+ EveCategoryId.FIGHTER,
92
+ EveCategoryId.DEPLOYABLE,
93
+ ],
94
+ published=True,
95
+ )
96
+ | Q(eve_group_id=EveGroupId.MINING_DRONE, published=True)
97
+ | Q(eve_group_id=EveGroupId.ORBITAL_INFRASTRUCTURE)
98
+ )
272
99
 
273
100
 
274
101
  class Tracker(models.Model):
@@ -514,6 +341,7 @@ class Tracker(models.Model):
514
341
  related_name="+",
515
342
  default=None,
516
343
  blank=True,
344
+ limit_choices_to=_require_attackers_ship_groups_query,
517
345
  help_text=(
518
346
  "Only include killmails where at least one attacker "
519
347
  "is flying one of these ship groups. "
@@ -524,6 +352,7 @@ class Tracker(models.Model):
524
352
  related_name="+",
525
353
  default=None,
526
354
  blank=True,
355
+ limit_choices_to=_require_attackers_ship_types_query,
527
356
  help_text=(
528
357
  "Only include killmails where at least one attacker "
529
358
  "is flying one of these ship types. "
@@ -534,6 +363,7 @@ class Tracker(models.Model):
534
363
  related_name="+",
535
364
  default=None,
536
365
  blank=True,
366
+ limit_choices_to=_require_attackers_weapon_groups_query,
537
367
  help_text=(
538
368
  "Only include killmails where at least one attacker "
539
369
  "is using one of these weapon groups. "
@@ -544,6 +374,7 @@ class Tracker(models.Model):
544
374
  related_name="+",
545
375
  default=None,
546
376
  blank=True,
377
+ limit_choices_to=_require_attackers_weapon_types_query,
547
378
  help_text=(
548
379
  "Only include killmails where at least one attacker "
549
380
  "is using one of these weapon types. "
@@ -554,6 +385,7 @@ class Tracker(models.Model):
554
385
  related_name="+",
555
386
  default=None,
556
387
  blank=True,
388
+ limit_choices_to=_require_victim_ship_groups_query,
557
389
  help_text=(
558
390
  "Only include killmails where victim is flying one of these ship groups. "
559
391
  ),
@@ -563,6 +395,7 @@ class Tracker(models.Model):
563
395
  related_name="+",
564
396
  default=None,
565
397
  blank=True,
398
+ limit_choices_to=_require_victim_ship_types_query,
566
399
  help_text=(
567
400
  "Only include killmails where victim is flying one of these ship types. "
568
401
  ),
@@ -0,0 +1,242 @@
1
+ """Webhooks models for killtracker."""
2
+
3
+ import json
4
+ from typing import List, Optional
5
+
6
+ import dhooks_lite
7
+ from simple_mq import SimpleMQ
8
+
9
+ from django.core.cache import cache
10
+ from django.db import models
11
+ from django.utils.translation import gettext_lazy as _
12
+
13
+ from allianceauth.services.hooks import get_extension_logger
14
+ from app_utils.allianceauth import get_redis_client
15
+ from app_utils.json import JSONDateTimeDecoder, JSONDateTimeEncoder
16
+ from app_utils.logging import LoggerAddTag
17
+ from app_utils.urls import static_file_absolute_url
18
+
19
+ from killtracker import APP_NAME, HOMEPAGE_URL, __title__, __version__
20
+ from killtracker.app_settings import KILLTRACKER_WEBHOOK_SET_AVATAR
21
+ from killtracker.exceptions import WebhookTooManyRequests
22
+ from killtracker.managers import WebhookManager
23
+
24
+ logger = LoggerAddTag(get_extension_logger(__name__), __title__)
25
+
26
+
27
+ class Webhook(models.Model):
28
+ """A webhook to receive messages"""
29
+
30
+ HTTP_TOO_MANY_REQUESTS = 429
31
+
32
+ class WebhookType(models.IntegerChoices):
33
+ """A webhook type."""
34
+
35
+ DISCORD = 1, _("Discord Webhook")
36
+
37
+ name = models.CharField(
38
+ max_length=64, unique=True, help_text="short name to identify this webhook"
39
+ )
40
+ webhook_type = models.IntegerField(
41
+ choices=WebhookType.choices,
42
+ default=WebhookType.DISCORD,
43
+ help_text="type of this webhook",
44
+ )
45
+ url = models.CharField(
46
+ max_length=255,
47
+ unique=True,
48
+ help_text=(
49
+ "URL of this webhook, e.g. "
50
+ "https://discordapp.com/api/webhooks/123456/abcdef"
51
+ ),
52
+ )
53
+ notes = models.TextField(
54
+ blank=True,
55
+ help_text="you can add notes about this webhook here if you want",
56
+ )
57
+ is_enabled = models.BooleanField(
58
+ default=True,
59
+ db_index=True,
60
+ help_text="whether notifications are currently sent to this webhook",
61
+ )
62
+ objects = WebhookManager()
63
+
64
+ def __init__(self, *args, **kwargs) -> None:
65
+ super().__init__(*args, **kwargs)
66
+ self.main_queue = self._create_queue("main")
67
+ self.error_queue = self._create_queue("error")
68
+
69
+ def __str__(self) -> str:
70
+ return self.name
71
+
72
+ def __repr__(self) -> str:
73
+ return f"{self.__class__.__name__}(id={self.id}, name='{self.name}')" # type: ignore
74
+
75
+ def __getstate__(self):
76
+ # Copy the object's state from self.__dict__ which contains
77
+ # all our instance attributes. Always use the dict.copy()
78
+ # method to avoid modifying the original state.
79
+ state = self.__dict__.copy()
80
+ # Remove the unpicklable entries.
81
+ del state["main_queue"]
82
+ del state["error_queue"]
83
+ return state
84
+
85
+ def __setstate__(self, state):
86
+ # Restore instance attributes (i.e., filename and lineno).
87
+ self.__dict__.update(state)
88
+ # Restore the previously opened file's state. To do so, we need to
89
+ # reopen it and read from it until the line count is restored.
90
+ self.main_queue = self._create_queue("main")
91
+ self.error_queue = self._create_queue("error")
92
+
93
+ def save(self, *args, **kwargs):
94
+ is_new = self.id is None # type: ignore
95
+ super().save(*args, **kwargs)
96
+ if is_new:
97
+ self.main_queue = self._create_queue("main")
98
+ self.error_queue = self._create_queue("error")
99
+
100
+ def _create_queue(self, suffix: str) -> Optional[SimpleMQ]:
101
+ redis_client = get_redis_client()
102
+ return (
103
+ SimpleMQ(redis_client, f"{__title__}_webhook_{self.pk}_{suffix}")
104
+ if self.pk
105
+ else None
106
+ )
107
+
108
+ def reset_failed_messages(self) -> int:
109
+ """moves all messages from error queue into main queue.
110
+ returns number of moved messages.
111
+ """
112
+ counter = 0
113
+ if self.error_queue and self.main_queue:
114
+ while True:
115
+ message = self.error_queue.dequeue()
116
+ if message is None:
117
+ break
118
+
119
+ self.main_queue.enqueue(message)
120
+ counter += 1
121
+
122
+ return counter
123
+
124
+ def enqueue_message(
125
+ self,
126
+ content: Optional[str] = None,
127
+ embeds: Optional[List[dhooks_lite.Embed]] = None,
128
+ tts: Optional[bool] = None,
129
+ username: Optional[str] = None,
130
+ avatar_url: Optional[str] = None,
131
+ ) -> int:
132
+ """Enqueues a message to be send with this webhook"""
133
+ if not self.main_queue:
134
+ return 0
135
+
136
+ username = __title__ if KILLTRACKER_WEBHOOK_SET_AVATAR else username
137
+ brand_url = static_file_absolute_url("killtracker/killtracker_logo.png")
138
+ avatar_url = brand_url if KILLTRACKER_WEBHOOK_SET_AVATAR else avatar_url
139
+ return self.main_queue.enqueue(
140
+ self._discord_message_asjson(
141
+ content=content,
142
+ embeds=embeds,
143
+ tts=tts,
144
+ username=username,
145
+ avatar_url=avatar_url,
146
+ )
147
+ )
148
+
149
+ @staticmethod
150
+ def _discord_message_asjson(
151
+ content: Optional[str] = None,
152
+ embeds: Optional[List[dhooks_lite.Embed]] = None,
153
+ tts: Optional[bool] = None,
154
+ username: Optional[str] = None,
155
+ avatar_url: Optional[str] = None,
156
+ ) -> str:
157
+ """Converts a Discord message to JSON and returns it
158
+
159
+ Raises ValueError if message is incomplete
160
+ """
161
+ if not content and not embeds:
162
+ raise ValueError("Message must have content or embeds to be valid")
163
+
164
+ if embeds:
165
+ embeds_list = [obj.asdict() for obj in embeds]
166
+ else:
167
+ embeds_list = None
168
+
169
+ message = {}
170
+ if content:
171
+ message["content"] = content
172
+ if embeds_list:
173
+ message["embeds"] = embeds_list
174
+ if tts:
175
+ message["tts"] = tts
176
+ if username:
177
+ message["username"] = username
178
+ if avatar_url:
179
+ message["avatar_url"] = avatar_url
180
+
181
+ return json.dumps(message, cls=JSONDateTimeEncoder)
182
+
183
+ def send_message_to_webhook(self, message_json: str) -> dhooks_lite.WebhookResponse:
184
+ """Send given message to webhook
185
+
186
+ Params
187
+ message_json: Discord message encoded in JSON
188
+ """
189
+ timeout = cache.ttl(self._blocked_cache_key()) # type: ignore
190
+ if timeout:
191
+ raise WebhookTooManyRequests(timeout)
192
+
193
+ message = json.loads(message_json, cls=JSONDateTimeDecoder)
194
+ if message.get("embeds"):
195
+ embeds = [
196
+ dhooks_lite.Embed.from_dict(embed_dict)
197
+ for embed_dict in message.get("embeds")
198
+ ]
199
+ else:
200
+ embeds = None
201
+ hook = dhooks_lite.Webhook(
202
+ url=self.url,
203
+ user_agent=dhooks_lite.UserAgent(
204
+ name=APP_NAME, url=HOMEPAGE_URL, version=__version__
205
+ ),
206
+ )
207
+ response = hook.execute(
208
+ content=message.get("content"),
209
+ embeds=embeds,
210
+ username=message.get("username"),
211
+ avatar_url=message.get("avatar_url"),
212
+ wait_for_response=True,
213
+ max_retries=0, # we will handle retries ourselves
214
+ )
215
+ logger.debug("headers: %s", response.headers)
216
+ logger.debug("status_code: %s", response.status_code)
217
+ logger.debug("content: %s", response.content)
218
+ if response.status_code == self.HTTP_TOO_MANY_REQUESTS:
219
+ logger.error(
220
+ "%s: Received too many requests error from API: %s",
221
+ self,
222
+ response.content,
223
+ )
224
+ try:
225
+ retry_after = int(response.headers["Retry-After"]) + 2
226
+ except (ValueError, KeyError):
227
+ retry_after = WebhookTooManyRequests.DEFAULT_RESET_AFTER
228
+ cache.set(
229
+ key=self._blocked_cache_key(), value="BLOCKED", timeout=retry_after
230
+ )
231
+ raise WebhookTooManyRequests(retry_after)
232
+ return response
233
+
234
+ def _blocked_cache_key(self) -> str:
235
+ return f"{__title__}_webhook_{self.pk}_blocked"
236
+
237
+ @staticmethod
238
+ def create_message_link(name: str, url: str) -> str:
239
+ """Create link for a Discord message"""
240
+ if name and url:
241
+ return f"[{str(name)}]({str(url)})"
242
+ return str(name)
@@ -31,7 +31,7 @@ from ..testdata.factories import (
31
31
  )
32
32
  from ..testdata.helpers import LoadTestDataMixin, load_killmail
33
33
 
34
- MODULE_PATH = "killtracker.models.trackers"
34
+ MODELS_PATH = "killtracker.models"
35
35
 
36
36
 
37
37
  def esi_get_route_origin_destination(origin, destination, **kwargs) -> list:
@@ -81,7 +81,7 @@ class TestTrackerCalculate(LoadTestDataMixin, NoSocketsTestCase):
81
81
  expected = {10000001, 10000002, 10000003, 10000004, 10000005}
82
82
  self.assertSetEqual(results, expected)
83
83
 
84
- @patch(MODULE_PATH + ".KILLTRACKER_KILLMAIL_MAX_AGE_FOR_TRACKER", 60)
84
+ @patch(MODELS_PATH + ".trackers.KILLTRACKER_KILLMAIL_MAX_AGE_FOR_TRACKER", 60)
85
85
  def test_excludes_older_killmails(self):
86
86
  tracker = TrackerFactory(
87
87
  name="Test",
@@ -144,7 +144,7 @@ class TestTrackerCalculate(LoadTestDataMixin, NoSocketsTestCase):
144
144
  expected = {10000002, 10000003, 10000004}
145
145
  self.assertSetEqual(results, expected)
146
146
 
147
- @patch("eveuniverse.models.esi")
147
+ @patch("eveuniverse.models.universe_2.esi")
148
148
  def test_can_filter_max_jumps(self, mock_esi):
149
149
  mock_esi.client.Routes.get_route_origin_destination.side_effect = (
150
150
  esi_get_route_origin_destination
@@ -160,7 +160,7 @@ class TestTrackerCalculate(LoadTestDataMixin, NoSocketsTestCase):
160
160
  expected = {10000102, 10000103}
161
161
  self.assertSetEqual(results, expected)
162
162
 
163
- @patch("eveuniverse.models.esi")
163
+ @patch("eveuniverse.models.universe_2.esi")
164
164
  def test_can_filter_max_distance(self, mock_esi):
165
165
  mock_esi.client.Routes.get_route_origin_destination.side_effect = (
166
166
  esi_get_route_origin_destination
@@ -572,7 +572,7 @@ class TestTrackerCalculate2(LoadTestDataMixin, NoSocketsTestCase):
572
572
  self.assertIsNone(result)
573
573
 
574
574
 
575
- @patch(MODULE_PATH + ".EveSolarSystem.jumps_to")
575
+ @patch(MODELS_PATH + ".trackers.EveSolarSystem.jumps_to")
576
576
  class TestTrackerCalculateTrackerInfo(LoadTestDataMixin, NoSocketsTestCase):
577
577
  def setUp(self) -> None:
578
578
  self.tracker = TrackerFactory(webhook=self.webhook_1)
@@ -667,8 +667,8 @@ class TestTrackerEnqueueKillmail(LoadTestDataMixin, TestCase):
667
667
  self.tracker = TrackerFactory(name="My Tracker", webhook=self.webhook_1)
668
668
  self.webhook_1.main_queue.clear()
669
669
 
670
- @patch(MODULE_PATH + ".KILLTRACKER_WEBHOOK_SET_AVATAR", True)
671
- @patch("eveuniverse.models.esi")
670
+ @patch(MODELS_PATH + ".webhooks.KILLTRACKER_WEBHOOK_SET_AVATAR", True)
671
+ @patch("eveuniverse.models.universe_2.esi")
672
672
  def test_normal(self, mock_esi):
673
673
  mock_esi.client.Routes.get_route_origin_destination.side_effect = (
674
674
  esi_get_route_origin_destination
@@ -694,8 +694,8 @@ class TestTrackerEnqueueKillmail(LoadTestDataMixin, TestCase):
694
694
  self.assertIn("Combat Battlecruiser", embed["description"])
695
695
  self.assertIn("Tracked ship types", embed["description"])
696
696
 
697
- @patch(MODULE_PATH + ".KILLTRACKER_WEBHOOK_SET_AVATAR", False)
698
- @patch("eveuniverse.models.esi")
697
+ @patch(MODELS_PATH + ".webhooks.KILLTRACKER_WEBHOOK_SET_AVATAR", False)
698
+ @patch("eveuniverse.models.universe_2.esi")
699
699
  def test_disabled_avatar(self, mock_esi):
700
700
  mock_esi.client.Routes.get_route_origin_destination.side_effect = (
701
701
  esi_get_route_origin_destination