import requests import time from django.core.management.base import BaseCommand from django.utils.dateparse import parse_date from store.models import Game, Set, Card class Command(BaseCommand): help = 'Populates the database with Pokémon TCG sets and cards using the TCGDex REST API (English).' def add_arguments(self, parser): parser.add_argument( '--clear', action='store_true', help='Clear existing Pokémon TCG cards and sets before populating.' ) parser.add_argument( '--duration', default='7', help='(Not full supported by TCGDex) Duration in days to look back. For now, this will just fetch all sets as TCGDex sets endpoint is not sorted by date.' ) def handle(self, *args, **options): self.stdout.write(self.style.SUCCESS('Starting Pokémon TCG population (via TCGDex)...')) # User Agent is good practice self.headers = {'User-Agent': 'ExampleTCGSite/1.0'} base_url = "https://api.tcgdex.net/v2/en" # 1. Ensure Game exists game, created = Game.objects.get_or_create( name="Pokémon Trading Card Game", defaults={'slug': 'pokemon-tcg'} ) if created: self.stdout.write(self.style.SUCCESS(f'Created Game: {game.name}')) else: self.stdout.write(f'Found Game: {game.name}') # Handle --clear if options['clear']: self.stdout.write(self.style.WARNING('Clearing existing Pokémon data...')) Card.objects.filter(set__game=game).delete() Set.objects.filter(game=game).delete() self.stdout.write(self.style.SUCCESS('Cleared Pokémon data.')) # 2. Fetch Sets self.stdout.write('Fetching sets from TCGDex...') try: # TCGDex /sets returns a list of minimal set objects response = requests.get(f"{base_url}/sets", headers=self.headers) response.raise_for_status() sets_data = response.json() except Exception as e: self.stdout.write(self.style.ERROR(f'Failed to fetch sets: {e}')) return self.stdout.write(f'Found {len(sets_data)} sets. Processing...') processed_sets = [] for s_data in sets_data: # s_data example: {"id": "base1", "name": "Base Set", ...} # TCGDex sets don't consistently provide releaseDate in the list view, # so we'll leave it null or updated if we fetched details (which we might do for cards). # For efficiency we might not fetch set details just for date if unnecessary. set_obj, created = Set.objects.update_or_create( code=s_data.get('id'), game=game, defaults={ 'name': s_data.get('name'), # 'release_date': None # Not available in simple list } ) processed_sets.append(set_obj) self.stdout.write(self.style.SUCCESS(f'Processed {len(processed_sets)} sets.')) # 3. Fetch Cards self.stdout.write('Fetching cards...') # We must iterate sets to get cards, as there isn't a robust "all cards new" stream without pagination headaches # on some APIs, and TCGDex structure favors set traversal. total_sets = len(processed_sets) for idx, set_obj in enumerate(processed_sets): self.stdout.write(f' [{idx+1}/{total_sets}] Fetching cards for set: {set_obj.name} ({set_obj.code})...') try: # Fetch Set Detail to get cards # Endpoint: /sets/{id} set_resp = requests.get(f"{base_url}/sets/{set_obj.code}", headers=self.headers) if set_resp.status_code == 404: self.stdout.write(self.style.WARNING(f' Set {set_obj.code} detail not found. Skipping.')) continue set_resp.raise_for_status() set_detail = set_resp.json() cards = set_detail.get('cards', []) except Exception as e: self.stdout.write(self.style.ERROR(f' Failed to fetch cards for {set_obj.name}: {e}')) continue self.stdout.write(f' Found {len(cards)} cards.') for c_data in cards: # c_data example: {"id": "base1-1", "localId": "1", "name": "Alakazam", "image": "..."} # Rarity is NOT in this list usually, requires fetching card detail. Skipping for speed. # Image URL: TCGDex gives a base URL usually, e.g. ".../base1/1" # Sometimes it has /high.png or /low.png supported. The provided 'image' field often works as is. # It might have extension like .png or just be the base. # The user-provided example curl showed "image": "https://assets.tcgdex.net/en/base/base1/1" # Those usually redirect to an image or handle extension. Let's append /high.png if we want best quality or try as is. # Actually, TCGDex assets usually need an extension. Let's assume the API provides a valid URL or we append. # Inspecting typical TCGDex response: "image": ".../1" (no extension). # Browsers handle it, but for our backend saving it might be tricky if it's not a direct file. # Let's save the URL as provided + "/high.png" as a guess for better quality if it doesn't have extension, # Or just use the provided one. # Update: TCGDex documentation often says: {image}/high.webp or {image}/low.webp base_image = c_data.get('image') image_url = f"{base_image}/high.webp" if base_image else '' Card.objects.update_or_create( scryfall_id=c_data.get('id'), defaults={ 'set': set_obj, 'name': c_data.get('name'), 'rarity': '', # specific call needed, simplifying 'image_url': image_url, 'collector_number': c_data.get('localId', ''), 'external_url': f"https://tcgdex.dev/cards/{c_data.get('id')}", # simplified assumption } ) # Rate limiting check - TCGDex is generous but good validation to not slam # time.sleep(0.1) self.stdout.write(self.style.SUCCESS('Finished Pokémon TCG population!'))