[boardgames] Add genres and categories
This commit is contained in:
@ -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)
|
- 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)
|
- 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:
|
** TODO Fix bug in fetching expansions for board games :boardgames:
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:ID: 17995312-e76e-4a50-b591-0eab78cb59ab
|
: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
|
boardgamegeek.exceptions.BGGApiError: invalid data for game id: 242117
|
||||||
#+end_src
|
#+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:
|
** DONE Exclude some board games from auto-expansion imports :boardgames:
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:ID: 51ffdf20-e732-4774-b781-c3501d26d46f
|
:ID: 51ffdf20-e732-4774-b781-c3501d26d46f
|
||||||
|
|||||||
@ -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}"
|
||||||
|
)
|
||||||
@ -351,8 +351,9 @@ class BoardGame(ScrobblableMixin):
|
|||||||
data.pop("max_players")
|
data.pop("max_players")
|
||||||
|
|
||||||
# Pop extra BGG metadata that isn't a model field
|
# Pop extra BGG metadata that isn't a model field
|
||||||
|
categories = data.pop("categories", [])
|
||||||
|
families = data.pop("families", [])
|
||||||
data.pop("mechanics", None)
|
data.pop("mechanics", None)
|
||||||
data.pop("categories", None)
|
|
||||||
data.pop("designers", None)
|
data.pop("designers", None)
|
||||||
data.pop("publishers", None)
|
data.pop("publishers", None)
|
||||||
data.pop("publisher", None)
|
data.pop("publisher", None)
|
||||||
@ -368,6 +369,11 @@ class BoardGame(ScrobblableMixin):
|
|||||||
) = BoardGamePublisher.objects.get_or_create(name=publisher_name)
|
) = BoardGamePublisher.objects.get_or_create(name=publisher_name)
|
||||||
self.save()
|
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
|
# Go get cover image if the URL is present
|
||||||
if cover_url and not self.cover:
|
if cover_url and not self.cover:
|
||||||
self.save_image_from_url(cover_url)
|
self.save_image_from_url(cover_url)
|
||||||
@ -417,6 +423,7 @@ class BoardGame(ScrobblableMixin):
|
|||||||
mechanics = bgg_data.pop("mechanics", [])
|
mechanics = bgg_data.pop("mechanics", [])
|
||||||
designers = bgg_data.pop("designers", [])
|
designers = bgg_data.pop("designers", [])
|
||||||
categories = bgg_data.pop("categories", [])
|
categories = bgg_data.pop("categories", [])
|
||||||
|
families = bgg_data.pop("families", [])
|
||||||
publishers = bgg_data.pop("publishers", [])
|
publishers = bgg_data.pop("publishers", [])
|
||||||
publisher = bgg_data.pop("publisher", [])
|
publisher = bgg_data.pop("publisher", [])
|
||||||
cover_url = bgg_data.pop("cover_url") or ""
|
cover_url = bgg_data.pop("cover_url") or ""
|
||||||
@ -459,6 +466,11 @@ class BoardGame(ScrobblableMixin):
|
|||||||
publisher, _ = BoardGamePublisher.objects.get_or_create(name=name)
|
publisher, _ = BoardGamePublisher.objects.get_or_create(name=name)
|
||||||
game.publishers.add(publisher)
|
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 expansions and not game.skip_expansions:
|
||||||
if defer_expansions:
|
if defer_expansions:
|
||||||
from boardgames.tasks import fetch_board_game_expansions
|
from boardgames.tasks import fetch_board_game_expansions
|
||||||
|
|||||||
@ -32,6 +32,7 @@ def lookup_boardgame_from_bgg(
|
|||||||
)
|
)
|
||||||
game_dict["mechanics"] = game.mechanics
|
game_dict["mechanics"] = game.mechanics
|
||||||
game_dict["categories"] = game.categories
|
game_dict["categories"] = game.categories
|
||||||
|
game_dict["families"] = game.families
|
||||||
game_dict["designers"] = game.designers
|
game_dict["designers"] = game.designers
|
||||||
game_dict["publishers"] = game.publishers
|
game_dict["publishers"] = game.publishers
|
||||||
if game.publishers:
|
if game.publishers:
|
||||||
|
|||||||
@ -83,6 +83,7 @@ def _mock_bgg_game(bggeek_id, title, expansions=None):
|
|||||||
playingtime = 60
|
playingtime = 60
|
||||||
mechanics = []
|
mechanics = []
|
||||||
categories = []
|
categories = []
|
||||||
|
families = []
|
||||||
designers = []
|
designers = []
|
||||||
publishers = []
|
publishers = []
|
||||||
|
|
||||||
|
|||||||
@ -46,6 +46,20 @@
|
|||||||
<p>
|
<p>
|
||||||
<a href="{{object.start_url}}">Play again</a>
|
<a href="{{object.start_url}}">Play again</a>
|
||||||
</p>
|
</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>
|
</div>
|
||||||
{% if charts %}
|
{% if charts %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
Reference in New Issue
Block a user