fix(game): show game name in dropdown labels (#43)

search_label built its label from sort_name, an optional sort key that
is blank for most games, so the Game and Related-game dropdowns in the
add-purchase form (and the session form and search API, which share the
property) showed a blank/"None" label. Use name, which is required.

Also route search_label and Purchase.full_name through label_with_details
so a missing year_released drops out of the parenthetical instead of
rendering a literal "None". (platform is never None at display time -
Game.save() substitutes the "Unspecified" sentinel.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-21 19:48:42 +02:00
parent 49601bb4fc
commit 1c0c067377
3 changed files with 43 additions and 11 deletions
+5 -8
View File
@@ -12,6 +12,7 @@ from django.template.defaultfilters import floatformat, pluralize, slugify
from django.utils import timezone
from common.time import format_duration
from common.utils import label_with_details
logger = logging.getLogger("games")
@@ -67,7 +68,7 @@ class Game(models.Model):
@property
def search_label(self) -> str:
return f"{self.sort_name} ({self.platform}, {self.year_released})"
return label_with_details(self.name, self.platform, self.year_released)
def finished(self):
return (
@@ -234,16 +235,12 @@ class Purchase(models.Model):
@property
def full_name(self):
additional_info = [
str(item)
for item in [
return label_with_details(
self.standardized_name,
f"{self.num_purchases} game{pluralize(self.num_purchases)}",
self.date_purchased,
self.standardized_price,
]
if item
]
return f"{self.standardized_name} ({', '.join(additional_info)})"
)
def is_game(self):
return self.type == self.GAME
+15
View File
@@ -63,6 +63,21 @@ class AddPurchasePricingTest(TestCase):
{self.game_a.id, self.game_b.id},
)
def test_full_name_keeps_parenthesized_detail_shape(self):
bundle = Purchase.objects.create(
name="Humble Bundle",
date_purchased=date(2025, 1, 1),
price=30,
price_currency="USD",
ownership_type=Purchase.DIGITAL,
)
bundle.games.set([self.game_a, self.game_b])
bundle.refresh_from_db()
full_name = bundle.full_name
self.assertTrue(full_name.startswith("Humble Bundle (2 games, "))
self.assertTrue(full_name.endswith(")"))
class SplitPurchaseTest(TestCase):
def setUp(self):
+20
View File
@@ -338,6 +338,26 @@ class SearchLabelTest(django.test.TestCase):
def test_format(self):
self.assertEqual(self.game.search_label, "Mario (Steam, 2020)")
def test_format_uses_name_not_sort_name(self):
game = Game.objects.create(
name="Tetris", sort_name="", platform=self.platform, year_released=1984
)
self.assertEqual(game.search_label, "Tetris (Steam, 1984)")
def test_format_omits_missing_year(self):
game = Game.objects.create(name="Tetris", platform=self.platform)
self.assertEqual(game.search_label, "Tetris (Steam)")
def test_format_uses_sentinel_platform_when_unset(self):
# Game.save() substitutes the "Unspecified" sentinel platform, so the
# platform part is never a literal "None"; only year_released can drop out.
game = Game.objects.create(name="Tetris", year_released=1984)
self.assertEqual(game.search_label, "Tetris (Unspecified, 1984)")
def test_format_with_sentinel_platform_and_no_year(self):
game = Game.objects.create(name="Tetris")
self.assertEqual(game.search_label, "Tetris (Unspecified)")
def test_choice_fields_use_search_label(self):
from games.forms import MultipleGameChoiceField, SingleGameChoiceField