[boardgames] Add genres and categories

This commit is contained in:
2026-07-04 11:41:24 -04:00
parent f373e98e3d
commit d78529efe2
6 changed files with 115 additions and 2 deletions

View File

@ -619,7 +619,6 @@ The Edit log form should have from top to bottom:
- Expansion ids (which should a multi-select widget of expansions for this game)
- Location (which should be a drop down of BoardGameLocations for this user)
** TODO Board games should have genres extracted from family data :boardgames:metadata:
** TODO Fix bug in fetching expansions for board games :boardgames:
:PROPERTIES:
:ID: 17995312-e76e-4a50-b591-0eab78cb59ab
@ -643,6 +642,11 @@ The Edit log form should have from top to bottom:
boardgamegeek.exceptions.BGGApiError: invalid data for game id: 242117
#+end_src
** TODO Board games should have genres extracted from family data :boardgames:metadata:
:PROPERTIES:
:ID: 7214b270-dccc-4b98-ac58-ff4f76c8cda9
:END:
** DONE Exclude some board games from auto-expansion imports :boardgames:
:PROPERTIES:
:ID: 51ffdf20-e732-4774-b781-c3501d26d46f

View File

@ -0,0 +1,81 @@
import logging
import time
from django.core.management.base import BaseCommand
logger = logging.getLogger(__name__)
class Command(BaseCommand):
help = "Refresh board game metadata from BGG (categories→genres, families→tags)"
def add_arguments(self, parser):
parser.add_argument(
"--commit",
action="store_true",
help="Persist changes to the database",
)
parser.add_argument(
"--force",
action="store_true",
help="Update all games even if they already have a published_date",
)
parser.add_argument(
"--batch-size",
type=int,
default=50,
help="Number of games to process per batch (default: 50)",
)
parser.add_argument(
"--sleep",
type=float,
default=1.0,
help="Seconds to sleep between API calls (default: 1.0)",
)
def handle(self, *args, **options):
from boardgames.models import BoardGame
commit = options["commit"]
force = options["force"]
batch_size = options["batch_size"]
sleep_secs = options["sleep"]
qs = BoardGame.objects.exclude(bggeek_id__isnull=True).exclude(bggeek_id="")
total = qs.count()
self.stdout.write(f"Found {total} board games with BGG IDs")
if not commit:
self.stdout.write(
"Dry run — no API calls will be made. Use --commit to run lookups."
)
return
enriched = 0
skipped = 0
for batch_num, offset in enumerate(range(0, total, batch_size)):
batch = qs[offset : offset + batch_size]
for game in batch:
try:
game.fix_metadata(force_update=force)
enriched += 1
except Exception as e:
self.stdout.write(
self.style.WARNING(
f" [SKIPPED] {game.title} (BGG {game.bggeek_id}): {e}"
)
)
skipped += 1
time.sleep(sleep_secs)
self.stdout.write(
f" Batch {batch_num + 1}: {offset + len(batch)}/{total}"
f"enriched: {enriched}, skipped: {skipped}"
)
self.stdout.write(
f"\nResults:\n"
f" Games enriched: {enriched}\n"
f" Games skipped: {skipped}"
)

View File

@ -351,8 +351,9 @@ class BoardGame(ScrobblableMixin):
data.pop("max_players")
# Pop extra BGG metadata that isn't a model field
categories = data.pop("categories", [])
families = data.pop("families", [])
data.pop("mechanics", None)
data.pop("categories", None)
data.pop("designers", None)
data.pop("publishers", None)
data.pop("publisher", None)
@ -368,6 +369,11 @@ class BoardGame(ScrobblableMixin):
) = BoardGamePublisher.objects.get_or_create(name=publisher_name)
self.save()
for cat in categories:
self.genre.add(cat.strip())
for fam in families:
self.tags.add(fam.strip())
# Go get cover image if the URL is present
if cover_url and not self.cover:
self.save_image_from_url(cover_url)
@ -417,6 +423,7 @@ class BoardGame(ScrobblableMixin):
mechanics = bgg_data.pop("mechanics", [])
designers = bgg_data.pop("designers", [])
categories = bgg_data.pop("categories", [])
families = bgg_data.pop("families", [])
publishers = bgg_data.pop("publishers", [])
publisher = bgg_data.pop("publisher", [])
cover_url = bgg_data.pop("cover_url") or ""
@ -459,6 +466,11 @@ class BoardGame(ScrobblableMixin):
publisher, _ = BoardGamePublisher.objects.get_or_create(name=name)
game.publishers.add(publisher)
for cat in categories:
game.genre.add(cat.strip())
for fam in families:
game.tags.add(fam.strip())
if expansions and not game.skip_expansions:
if defer_expansions:
from boardgames.tasks import fetch_board_game_expansions

View File

@ -32,6 +32,7 @@ def lookup_boardgame_from_bgg(
)
game_dict["mechanics"] = game.mechanics
game_dict["categories"] = game.categories
game_dict["families"] = game.families
game_dict["designers"] = game.designers
game_dict["publishers"] = game.publishers
if game.publishers:

View File

@ -83,6 +83,7 @@ def _mock_bgg_game(bggeek_id, title, expansions=None):
playingtime = 60
mechanics = []
categories = []
families = []
designers = []
publishers = []

View File

@ -46,6 +46,20 @@
<p>
<a href="{{object.start_url}}">Play again</a>
</p>
{% if object.genre.all %}
<p>Genres:
{% for tag in object.genre.all %}
<a href="{% url 'boardgames:boardgame_list' %}?genre={{ tag.name|urlencode }}" class="badge bg-secondary text-decoration-none">{{ tag.name }}</a>
{% endfor %}
</p>
{% endif %}
{% if object.tags.all %}
<p>Tags:
{% for tag in object.tags.all %}
<a href="{% url 'boardgames:boardgame_list' %}?tag={{ tag.name|urlencode }}" class="badge bg-secondary text-decoration-none">{{ tag.name }}</a>
{% endfor %}
</p>
{% endif %}
</div>
{% if charts %}
<div class="row">