feat(scouting): add wingspan filters and saved searches mvp
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.management import call_command
|
||||
@ -8,7 +9,19 @@ from django.db.migrations.executor import MigrationExecutor
|
||||
from django.test import TestCase, TransactionTestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from .models import Competition, FavoritePlayer, Player, PlayerNote, PlayerSeason, PlayerSeasonStats, Role, Season, Specialty, Team
|
||||
from .models import (
|
||||
Competition,
|
||||
FavoritePlayer,
|
||||
Player,
|
||||
PlayerNote,
|
||||
PlayerSeason,
|
||||
PlayerSeasonStats,
|
||||
Role,
|
||||
SavedSearch,
|
||||
Season,
|
||||
Specialty,
|
||||
Team,
|
||||
)
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
@ -34,6 +47,7 @@ class ScoutingSearchViewsTests(TestCase):
|
||||
position="PG",
|
||||
height_cm=Decimal("188.00"),
|
||||
weight_kg=Decimal("82.00"),
|
||||
wingspan_cm=Decimal("194.00"),
|
||||
nationality="IT",
|
||||
)
|
||||
cls.player_pg.roles.add(cls.role_playmaker)
|
||||
@ -45,6 +59,7 @@ class ScoutingSearchViewsTests(TestCase):
|
||||
position="SF",
|
||||
height_cm=Decimal("201.00"),
|
||||
weight_kg=Decimal("95.00"),
|
||||
wingspan_cm=Decimal("211.00"),
|
||||
)
|
||||
cls.player_wing.roles.add(cls.role_3d)
|
||||
cls.player_wing.specialties.add(cls.specialty_offball)
|
||||
@ -127,6 +142,15 @@ class ScoutingSearchViewsTests(TestCase):
|
||||
self.assertContains(response, self.player_pg.full_name)
|
||||
self.assertNotContains(response, self.player_wing.full_name)
|
||||
|
||||
def test_filter_by_wingspan_thresholds(self):
|
||||
response = self.client.get(
|
||||
reverse("scouting:player_list"),
|
||||
{"min_wingspan_cm": "205"},
|
||||
)
|
||||
|
||||
self.assertContains(response, self.player_wing.full_name)
|
||||
self.assertNotContains(response, self.player_pg.full_name)
|
||||
|
||||
def test_filter_by_context_fields_and_stats(self):
|
||||
response = self.client.get(
|
||||
reverse("scouting:player_list"),
|
||||
@ -514,6 +538,114 @@ class FavoritePlayerViewsTests(TestCase):
|
||||
self.assertRedirects(response, f"{reverse('login')}?next={reverse('scouting:favorites_list')}")
|
||||
|
||||
|
||||
class SavedSearchViewsTests(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.user = User.objects.create_user(username="saved_owner", password="pass12345")
|
||||
cls.other_user = User.objects.create_user(username="saved_other", password="pass12345")
|
||||
cls.player_guard = Player.objects.create(
|
||||
full_name="Saved Guard",
|
||||
birth_date=date(2002, 7, 7),
|
||||
position="PG",
|
||||
height_cm=Decimal("188.00"),
|
||||
weight_kg=Decimal("82.00"),
|
||||
wingspan_cm=Decimal("196.00"),
|
||||
)
|
||||
cls.player_wing = Player.objects.create(
|
||||
full_name="Saved Wing",
|
||||
birth_date=date(2000, 9, 9),
|
||||
position="SF",
|
||||
height_cm=Decimal("203.00"),
|
||||
weight_kg=Decimal("95.00"),
|
||||
wingspan_cm=Decimal("213.00"),
|
||||
)
|
||||
|
||||
def test_logged_in_user_can_save_search(self):
|
||||
self.client.force_login(self.user)
|
||||
response = self.client.post(
|
||||
reverse("scouting:save_search"),
|
||||
{
|
||||
"saved_search_name": "Long Wings",
|
||||
"position": "SF",
|
||||
"min_wingspan_cm": "210",
|
||||
"sort": "height_desc",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertIn(f"{reverse('scouting:player_list')}?", response["Location"])
|
||||
self.assertIn("sort=height_desc", response["Location"])
|
||||
self.assertIn("position=SF", response["Location"])
|
||||
self.assertIn("min_wingspan_cm=210", response["Location"])
|
||||
saved = SavedSearch.objects.get(user=self.user, name="Long Wings")
|
||||
self.assertEqual(saved.params["position"], "SF")
|
||||
self.assertEqual(saved.params["min_wingspan_cm"], "210")
|
||||
|
||||
def test_saved_searches_are_user_scoped(self):
|
||||
SavedSearch.objects.create(user=self.user, name="Mine", params={"position": "PG"})
|
||||
SavedSearch.objects.create(user=self.other_user, name="Other", params={"position": "SF"})
|
||||
self.client.force_login(self.user)
|
||||
|
||||
response = self.client.get(reverse("scouting:player_list"))
|
||||
|
||||
self.assertContains(response, "Mine")
|
||||
self.assertNotContains(response, "Other")
|
||||
|
||||
def test_logged_in_user_can_rerun_saved_search(self):
|
||||
saved = SavedSearch.objects.create(user=self.user, name="Wingspan Hunt", params={"min_wingspan_cm": "210"})
|
||||
self.client.force_login(self.user)
|
||||
|
||||
response = self.client.get(f"{reverse('scouting:player_list')}?{urlencode(saved.params)}")
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, self.player_wing.full_name)
|
||||
self.assertNotContains(response, self.player_guard.full_name)
|
||||
|
||||
def test_logged_in_user_can_delete_saved_search(self):
|
||||
saved = SavedSearch.objects.create(user=self.user, name="Delete Me", params={"position": "PG"})
|
||||
self.client.force_login(self.user)
|
||||
|
||||
response = self.client.post(
|
||||
reverse("scouting:delete_saved_search", args=[saved.id]),
|
||||
{"position": "PG"},
|
||||
)
|
||||
|
||||
self.assertRedirects(response, f"{reverse('scouting:player_list')}?position=PG")
|
||||
self.assertFalse(SavedSearch.objects.filter(pk=saved.id).exists())
|
||||
|
||||
def test_unauthenticated_user_cannot_save_or_delete_saved_searches(self):
|
||||
save_response = self.client.post(reverse("scouting:save_search"), {"saved_search_name": "No Auth"})
|
||||
self.assertRedirects(save_response, f"{reverse('login')}?next={reverse('scouting:save_search')}")
|
||||
|
||||
saved = SavedSearch.objects.create(user=self.user, name="Auth Required", params={"position": "PG"})
|
||||
delete_response = self.client.post(reverse("scouting:delete_saved_search", args=[saved.id]))
|
||||
self.assertRedirects(
|
||||
delete_response,
|
||||
f"{reverse('login')}?next={reverse('scouting:delete_saved_search', args=[saved.id])}",
|
||||
)
|
||||
|
||||
def test_combined_filters_saved_search_and_rerun(self):
|
||||
self.client.force_login(self.user)
|
||||
save_response = self.client.post(
|
||||
reverse("scouting:save_search"),
|
||||
{
|
||||
"saved_search_name": "SF Long Wings",
|
||||
"position": "SF",
|
||||
"min_wingspan_cm": "212",
|
||||
"sort": "height_desc",
|
||||
},
|
||||
)
|
||||
self.assertEqual(save_response.status_code, 302)
|
||||
saved = SavedSearch.objects.get(user=self.user, name="SF Long Wings")
|
||||
|
||||
rerun_response = self.client.get(f"{reverse('scouting:player_list')}?position=SF&min_wingspan_cm=212&sort=height_desc")
|
||||
|
||||
self.assertEqual(rerun_response.status_code, 200)
|
||||
self.assertContains(rerun_response, self.player_wing.full_name)
|
||||
self.assertNotContains(rerun_response, self.player_guard.full_name)
|
||||
self.assertEqual(saved.params["sort"], "height_desc")
|
||||
|
||||
|
||||
class PlayerNoteViewsTests(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
Reference in New Issue
Block a user