from datetime import date from rest_framework import status from rest_framework.response import Response from rest_framework.views import APIView from api.models import Aspect, BuildOfDay, Hero, Item from api.serializers import ( HeroSerializer, ItemSerializer, RandomizeBuildRequestSerializer, ) from .data import generate_skill_build class HeroesListView(APIView): """ GET: Return all available heroes for selection. """ def get(self, request): heroes = Hero.objects.all().order_by("name") return Response(HeroSerializer(heroes, many=True).data, status=status.HTTP_200_OK) class RandomizeBuildView(APIView): """ POST: Generate a random Dota 2 build with hero, items, and optionally skills/aspects. """ def get_serializer(self, *args, **kwargs): return RandomizeBuildRequestSerializer(*args, **kwargs) def post(self, request): serializer = RandomizeBuildRequestSerializer(data=request.data) if not serializer.is_valid(): return Response( {"message": self._format_errors(serializer.errors)}, status=status.HTTP_400_BAD_REQUEST, ) validated_data = serializer.validated_data include_skills = validated_data["includeSkills"] include_aspect = validated_data["includeAspect"] items_count = validated_data["itemsCount"] hero_id = validated_data.get("heroId") hero_count = Hero.objects.count() item_count = Item.objects.count() if hero_count == 0: return Response( {"message": "No heroes available. Load data first."}, status=status.HTTP_400_BAD_REQUEST, ) if item_count < items_count: return Response( {"message": f"Not enough items available. Requested {items_count}, found {item_count}."}, status=status.HTTP_400_BAD_REQUEST, ) if hero_id: hero_obj = Hero.objects.filter(id=hero_id).first() if not hero_obj: return Response( {"message": f"Hero with id {hero_id} not found."}, status=status.HTTP_400_BAD_REQUEST, ) else: hero_obj = Hero.objects.order_by("?").first() item_objs = Item.objects.order_by("?")[:items_count] response_data = { "hero": HeroSerializer(hero_obj).data, "items": ItemSerializer(item_objs, many=True).data, } if include_skills: response_data["skillBuild"] = generate_skill_build() if include_aspect: hero_aspects = Aspect.objects.filter(hero=hero_obj) if hero_aspects.exists(): aspect_obj = hero_aspects.order_by("?").first() response_data["aspect"] = aspect_obj.name return Response(response_data, status=status.HTTP_200_OK) def _format_errors(self, errors: dict) -> str: """Format serializer errors into a readable message.""" messages = [] for field, field_errors in errors.items(): for error in field_errors: if field == "itemsCount" and "greater than or equal to" in str(error): messages.append("itemsCount must be at least 1.") elif "valid" in str(error).lower() or "required" in str(error).lower(): if field in ("includeSkills", "includeAspect"): messages.append("includeSkills and includeAspect must be boolean.") elif field == "itemsCount": messages.append("itemsCount must be a number.") else: messages.append(f"{field}: {error}") return messages[0] if messages else "Invalid request data." class BuildOfDayView(APIView): """ GET: Return the build of the day. Generates one if it doesn't exist for today. """ ITEMS_COUNT = 6 def get(self, request): today = date.today() build = BuildOfDay.objects.filter(date=today).first() if not build: build = self._generate_build_of_day(today) if build is None: return Response( {"message": "Unable to generate build. Load data first."}, status=status.HTTP_400_BAD_REQUEST, ) response_data = { "date": build.date.isoformat(), "hero": HeroSerializer(build.hero).data, "items": ItemSerializer(build.items.all(), many=True).data, "skillBuild": build.skill_build, } if build.aspect: response_data["aspect"] = build.aspect return Response(response_data, status=status.HTTP_200_OK) def _generate_build_of_day(self, today: date) -> BuildOfDay | None: """Generate and save a new build of the day.""" hero_count = Hero.objects.count() item_count = Item.objects.count() if hero_count == 0 or item_count < self.ITEMS_COUNT: return None hero_obj = Hero.objects.order_by("?").first() item_objs = list(Item.objects.order_by("?")[: self.ITEMS_COUNT]) aspect_name = None hero_aspects = Aspect.objects.filter(hero=hero_obj) if hero_aspects.exists(): aspect_obj = hero_aspects.order_by("?").first() aspect_name = aspect_obj.name build = BuildOfDay.objects.create( date=today, hero=hero_obj, skill_build=generate_skill_build(), aspect=aspect_name, ) build.items.set(item_objs) return build