From d1d079e2c39402ce6c41f0e10778f0d29324feb0 Mon Sep 17 00:00:00 2001
From: freyamade
Date: Tue, 6 May 2025 11:24:10 +0200
Subject: [PATCH 1/8] add tests for what I assume is the issue from toonie
---
backend/api/tests/test_bis_list.py | 98 ++++++++++++++++++++++++++++++
1 file changed, 98 insertions(+)
diff --git a/backend/api/tests/test_bis_list.py b/backend/api/tests/test_bis_list.py
index 938d4dd6..b8209ba7 100644
--- a/backend/api/tests/test_bis_list.py
+++ b/backend/api/tests/test_bis_list.py
@@ -245,6 +245,51 @@ def test_create_with_sync(self):
self.assertEqual(sync_bis.current_body_id, self.gear_id_map['Radiant Host'])
self.assertEqual(non_sync_bis.current_body_id, self.gear_id_map['Moonward'])
+ def test_create_with_ring_swap(self):
+ """
+ Create a new BIS List for the character
+ """
+ url = reverse('api:bis_collection', kwargs={'character_id': self.char.pk})
+ self.client.force_authenticate(self.char.user)
+
+ # Try one with PLD first
+ data = {
+ 'job_id': 'PLD',
+ 'bis_mainhand_id': self.gear_id_map['Augmented Historia'],
+ 'bis_offhand_id': self.gear_id_map['Augmented Historia'],
+ 'bis_head_id': self.gear_id_map['Babyface Champion'],
+ 'bis_body_id': self.gear_id_map['Augmented Historia'],
+ 'bis_hands_id': self.gear_id_map['Babyface Champion'],
+ 'bis_legs_id': self.gear_id_map['Babyface Champion'],
+ 'bis_feet_id': self.gear_id_map['Babyface Champion'],
+ 'bis_earrings_id': self.gear_id_map['Augmented Historia'],
+ 'bis_necklace_id': self.gear_id_map['Augmented Historia'],
+ 'bis_bracelet_id': self.gear_id_map['Augmented Historia'],
+ 'bis_right_ring_id': self.gear_id_map['Augmented Historia'],
+ 'bis_left_ring_id': self.gear_id_map['Babyface Champion'],
+ 'current_mainhand_id': self.gear_id_map['Ceremonial'],
+ 'current_offhand_id': self.gear_id_map['Ceremonial'],
+ 'current_head_id': self.gear_id_map['Ceremonial'],
+ 'current_body_id': self.gear_id_map['Ceremonial'],
+ 'current_hands_id': self.gear_id_map['Ceremonial'],
+ 'current_legs_id': self.gear_id_map['Ceremonial'],
+ 'current_feet_id': self.gear_id_map['Ceremonial'],
+ 'current_earrings_id': self.gear_id_map['Ceremonial'],
+ 'current_necklace_id': self.gear_id_map['Ceremonial'],
+ 'current_bracelet_id': self.gear_id_map['Ceremonial'],
+ 'current_right_ring_id': self.gear_id_map['Babyface Champion'],
+ 'current_left_ring_id': self.gear_id_map['Ceremonial'],
+ 'external_link': '',
+ 'name': 'Swap the Rings',
+ }
+
+ response = self.client.post(url, data)
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.content)
+ self.assertEqual(BISList.objects.count(), 1)
+ obj = BISList.objects.first()
+ # Ensure that the request to create has swapped the rings to the correct location
+ self.assertEqual(obj.bis_left_ring_id, obj.current_left_ring_id)
+
def test_404(self):
"""
Test all situations where the endpoint would respond with a 404;
@@ -429,6 +474,59 @@ def test_update_400(self):
self.assertEqual(content['current_mainhand_id'], [invalid_gear])
self.assertEqual(content['name'], ['Ensure this field has no more than 64 characters.'])
+ def test_update_with_ring_swap(self):
+ """
+ Update the existing BIS List with a PUT request
+ """
+ url = reverse('api:bis_resource', kwargs={'character_id': self.char.pk, 'pk': self.bis.pk})
+ self.client.force_authenticate(self.char.user)
+
+ # Get modern gear for the update
+ tome_gear = Gear.objects.get(name='Augmented Historia').pk
+ raid_gear = Gear.objects.get(name='Babyface Champion', has_weapon=False).pk
+ crafted_gear = Gear.objects.get(name='Ceremonial').pk
+
+ # Send an update request with the rings swapped, ensure they are bis when the request is finished
+ data = {
+ 'job_id': 'PLD',
+ 'bis_mainhand_id': tome_gear,
+ 'bis_offhand_id': tome_gear,
+ 'bis_head_id': raid_gear,
+ 'bis_body_id': tome_gear,
+ 'bis_hands_id': raid_gear,
+ 'bis_legs_id': raid_gear,
+ 'bis_feet_id': raid_gear,
+ 'bis_earrings_id': tome_gear,
+ 'bis_necklace_id': tome_gear,
+ 'bis_bracelet_id': tome_gear,
+ 'bis_right_ring_id': tome_gear,
+ 'bis_left_ring_id': raid_gear,
+ 'current_mainhand_id': crafted_gear,
+ 'current_offhand_id': crafted_gear,
+ 'current_head_id': crafted_gear,
+ 'current_body_id': crafted_gear,
+ 'current_hands_id': crafted_gear,
+ 'current_legs_id': crafted_gear,
+ 'current_feet_id': crafted_gear,
+ 'current_earrings_id': crafted_gear,
+ 'current_necklace_id': crafted_gear,
+ 'current_bracelet_id': crafted_gear,
+ 'current_right_ring_id': raid_gear,
+ 'current_left_ring_id': tome_gear,
+ 'external_link': None,
+ 'name': 'Update ring swap c:',
+ }
+
+ response = self.client.put(url, data)
+ self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT, response.content)
+
+ # Assert the rings are correct
+ self.bis.refresh_from_db()
+ self.assertEqual(self.bis.bis_right_ring_id, tome_gear)
+ self.assertEqual(self.bis.bis_left_ring_id, raid_gear)
+ self.assertEqual(self.bis.bis_right_ring_id, self.bis.current_right_ring_id)
+ self.assertEqual(self.bis.bis_left_ring_id, self.bis.current_left_ring_id)
+
def test_404(self):
"""
Test all situations where the endpoint would respond with a 404;
From 94c3db55d1b4555deeb27674635e300b4a7c1c81 Mon Sep 17 00:00:00 2001
From: freyamade
Date: Tue, 6 May 2025 11:43:52 +0200
Subject: [PATCH 2/8] implement ring swapping in the bislist pre_save hook
---
backend/api/models/bis_list.py | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/backend/api/models/bis_list.py b/backend/api/models/bis_list.py
index b81b68b4..e19751b0 100644
--- a/backend/api/models/bis_list.py
+++ b/backend/api/models/bis_list.py
@@ -7,6 +7,7 @@
import auto_prefetch
from django.db import models
from django.db.models import Q
+from django.dispatch import receiver
class BISList(auto_prefetch.Model):
@@ -181,3 +182,29 @@ def needs_armour_augments(gear_name: str) -> models.QuerySet:
| (Q(bis_legs__name=gear_name) & ~Q(current_legs__name=gear_name))
| (Q(bis_feet__name=gear_name) & ~Q(current_feet__name=gear_name)),
)
+
+
+@receiver(models.signals.pre_save, sender=BISList)
+def bis_list_ring_swap(sender, instance: BISList, *args, **kwargs):
+ """
+ Check if either of the current_ring slots on the to-be-saved BIS List need to be swapped.
+ Swapping is required if one of the bis slots matches the opposite current slot and doesn't match its own.
+ """
+ needs_swapping = False
+ if (
+ instance.bis_right_ring_id != instance.current_right_ring_id
+ and instance.bis_right_ring_id == instance.current_left_ring_id
+ ):
+ needs_swapping = True
+
+ if (
+ instance.bis_left_ring_id != instance.current_left_ring_id
+ and instance.bis_left_ring_id == instance.current_right_ring_id
+ ):
+ needs_swapping = True
+
+ if not needs_swapping:
+ return
+
+ # Swap the current rings to make it work
+ instance.current_left_ring, instance.current_right_ring = instance.current_right_ring, instance.current_left_ring
From 7a9e41a85172e790a11d9e051f1242c44ab7209d Mon Sep 17 00:00:00 2001
From: freyamade
Date: Tue, 6 May 2025 11:50:54 +0200
Subject: [PATCH 3/8] add a little message to the bottom of the loot solver if
the user isn't using the PFLM to inform that it would help and link to
settings
---
frontend/src/components/loot/solver.vue | 3 +++
1 file changed, 3 insertions(+)
diff --git a/frontend/src/components/loot/solver.vue b/frontend/src/components/loot/solver.vue
index 33ea029d..7998a824 100644
--- a/frontend/src/components/loot/solver.vue
+++ b/frontend/src/components/loot/solver.vue
@@ -203,6 +203,9 @@
+
+
+ If you would like to auto-fill the Loot for a fight, try enabling the "Per Fight Loot Manager" in your Settings page!
From 16d28c1cf7bac0c3e9785fd9c869c5cd7d5daebf Mon Sep 17 00:00:00 2001
From: freyamade
Date: Tue, 6 May 2025 11:54:43 +0200
Subject: [PATCH 4/8] update changelog
---
frontend/src/components/modals/changelog.vue | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/frontend/src/components/modals/changelog.vue b/frontend/src/components/modals/changelog.vue
index b4791c5d..48148f54 100644
--- a/frontend/src/components/modals/changelog.vue
+++ b/frontend/src/components/modals/changelog.vue
@@ -14,12 +14,14 @@
{{ version }}
expand_more Quality of Life expand_more
- Team Leaders can now also send Lodestone Update requests via the Team Page for other Team Members, in case people are forgetting to update their BIS.
-
- Thanks toonie for the suggestion!
+ BIS List updates will now automatically swap the current ring slots if, for example, the current left ring matches the bis right ring.
+
+ - This should hopefully make the Loot Solver / Manager break less often.
+ - This deployment has also re-saved every BIS List in the system to automatically update the lists for everyone.
+ - Thanks toonie for the suggestion!
+
-
- expand_more Fixes expand_more
- Improved error handling in the LootSolver when the Team's Tier mismatched with the BIS Lists in it.
+ Added a message to the Loot Solver that links to the Settings page if you are not using the Per-Fight Loot Manager, just in case you want to use auto-assign but don't know how.
From 1f82250333fc0820f89461de63cd8de962b68684 Mon Sep 17 00:00:00 2001
From: freyamade
Date: Tue, 6 May 2025 18:27:42 +0200
Subject: [PATCH 5/8] add a command to call .save() on every bis list
---
.../management/commands/save_all_bis_lists.py | 10 ++++
backend/api/tests/test_management.py | 59 +++++++++++++++++++
2 files changed, 69 insertions(+)
create mode 100644 backend/api/management/commands/save_all_bis_lists.py
diff --git a/backend/api/management/commands/save_all_bis_lists.py b/backend/api/management/commands/save_all_bis_lists.py
new file mode 100644
index 00000000..657d8f73
--- /dev/null
+++ b/backend/api/management/commands/save_all_bis_lists.py
@@ -0,0 +1,10 @@
+from django.core.management.base import BaseCommand
+from api.models import BISList
+
+
+class Command(BaseCommand):
+ help = 'Call .save() on all BISLists to handle the ring swapping.'
+
+ def handle(self, *args, **options):
+ for obj in BISList.objects.all():
+ obj.save()
diff --git a/backend/api/tests/test_management.py b/backend/api/tests/test_management.py
index 0ad56770..ef7e065e 100644
--- a/backend/api/tests/test_management.py
+++ b/backend/api/tests/test_management.py
@@ -132,3 +132,62 @@ def test_notification_setup(self):
self.assertTrue('verify_success' in settings.notifications)
self.assertTrue(settings.notifications['verify_success'])
self.assertFalse(settings.notifications['verify_fail'])
+
+ def test_save_bis_list_ring_swap(self):
+ """
+ Create a BISList with rings swapped, then run the management command
+ """
+ call_command('seed', stdout=StringIO())
+ char = models.Character.objects.create(
+ avatar_url='https://img.savageaim.com/abcde',
+ lodestone_id=1234567890,
+ user=self._get_user(),
+ name='Team Lead',
+ verified=True,
+ world='Lich',
+ )
+
+ # Next, create two BIS lists for each character
+ raid_weapon = models.Gear.objects.get(item_level=605, name='Asphodelos')
+ raid_gear = models.Gear.objects.get(item_level=600, has_weapon=False)
+ tome_gear = models.Gear.objects.get(item_level=600, has_weapon=True)
+ crafted = models.Gear.objects.get(name='Classical')
+ bis = models.BISList.objects.create(
+ bis_body=raid_gear,
+ bis_bracelet=raid_gear,
+ bis_earrings=raid_gear,
+ bis_feet=raid_gear,
+ bis_hands=tome_gear,
+ bis_head=tome_gear,
+ bis_legs=tome_gear,
+ bis_mainhand=raid_weapon,
+ bis_necklace=tome_gear,
+ bis_offhand=raid_weapon,
+ bis_left_ring=tome_gear,
+ bis_right_ring=raid_gear,
+ current_body=crafted,
+ current_bracelet=crafted,
+ current_earrings=crafted,
+ current_feet=crafted,
+ current_hands=crafted,
+ current_head=crafted,
+ current_legs=crafted,
+ current_mainhand=crafted,
+ current_necklace=crafted,
+ current_offhand=crafted,
+ current_left_ring=crafted,
+ current_right_ring=tome_gear,
+ job_id='SGE',
+ owner=char,
+ )
+ models.BISList.objects.update(
+ current_right_ring_id=tome_gear.id,
+ current_left_ring_id=crafted.id,
+ )
+ bis.refresh_from_db()
+ self.assertNotEqual(bis.current_right_ring_id, crafted.id)
+ self.assertNotEqual(bis.current_left_ring_id, tome_gear.id)
+ call_command('save_all_bis_lists')
+ bis.refresh_from_db()
+ self.assertEqual(bis.current_right_ring_id, crafted.id)
+ self.assertEqual(bis.current_left_ring_id, tome_gear.id)
From 59d0ea7304cb8b8cf10fee9d1c615b98da50bfd7 Mon Sep 17 00:00:00 2001
From: freyamade
Date: Tue, 6 May 2025 18:27:54 +0200
Subject: [PATCH 6/8] finalise changelog update
---
frontend/src/components/modals/changelog.vue | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/components/modals/changelog.vue b/frontend/src/components/modals/changelog.vue
index 48148f54..0fd67b91 100644
--- a/frontend/src/components/modals/changelog.vue
+++ b/frontend/src/components/modals/changelog.vue
@@ -17,7 +17,7 @@
BIS List updates will now automatically swap the current ring slots if, for example, the current left ring matches the bis right ring.
- This should hopefully make the Loot Solver / Manager break less often.
- - This deployment has also re-saved every BIS List in the system to automatically update the lists for everyone.
+ - This deployment has also re-saved every BIS List in the system to automatically update the lists for everyone. (I really hope this hasn't broken anything...)
- Thanks toonie for the suggestion!
From 630215ddd22cb85aef385a7534dbb3ba6ff30f56 Mon Sep 17 00:00:00 2001
From: freyamade
Date: Tue, 6 May 2025 18:28:15 +0200
Subject: [PATCH 7/8] bump version
---
backend/backend/__init__.py | 2 +-
frontend/.env | 2 +-
frontend/src/main.ts | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/backend/backend/__init__.py b/backend/backend/__init__.py
index 88e86fcd..4b3f510e 100644
--- a/backend/backend/__init__.py
+++ b/backend/backend/__init__.py
@@ -2,4 +2,4 @@
from .celery import app as celery_app
-VERSION = '20250417'
+VERSION = '20250506'
diff --git a/frontend/.env b/frontend/.env
index 7586fb24..a99a58f8 100644
--- a/frontend/.env
+++ b/frontend/.env
@@ -1 +1 @@
-VUE_APP_VERSION="20250417"
+VUE_APP_VERSION="20250506"
diff --git a/frontend/src/main.ts b/frontend/src/main.ts
index c7c48d40..66612cab 100644
--- a/frontend/src/main.ts
+++ b/frontend/src/main.ts
@@ -30,7 +30,7 @@ Sentry.init({
Vue,
dsn: 'https://06f41b525a40497a848fb726f6d03244@o242258.ingest.sentry.io/6180221',
logErrors: true,
- release: 'savageaim@20250417',
+ release: 'savageaim@20250506',
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration(),
From c901cf07814d71da2b5865efef1a5c21842b251e Mon Sep 17 00:00:00 2001
From: freyamade
Date: Tue, 6 May 2025 18:31:19 +0200
Subject: [PATCH 8/8] lint python
---
backend/api/models/bis_list.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/backend/api/models/bis_list.py b/backend/api/models/bis_list.py
index e19751b0..2c34287a 100644
--- a/backend/api/models/bis_list.py
+++ b/backend/api/models/bis_list.py
@@ -192,13 +192,13 @@ def bis_list_ring_swap(sender, instance: BISList, *args, **kwargs):
"""
needs_swapping = False
if (
- instance.bis_right_ring_id != instance.current_right_ring_id
+ instance.bis_right_ring_id != instance.current_right_ring_id
and instance.bis_right_ring_id == instance.current_left_ring_id
):
needs_swapping = True
if (
- instance.bis_left_ring_id != instance.current_left_ring_id
+ instance.bis_left_ring_id != instance.current_left_ring_id
and instance.bis_left_ring_id == instance.current_right_ring_id
):
needs_swapping = True