added deck model and creation endpoint

This commit is contained in:
Alexander Karpov 2022-06-06 20:15:45 +03:00
parent acc37f8e82
commit ba04ce8347
9 changed files with 269 additions and 51 deletions

0
game/api/__init__.py Normal file
View File

0
game/api/v1/__init__.py Normal file
View File

View File

@ -0,0 +1,94 @@
from abc import ABC
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from game.models import Hero, Player, HeroInDeck, Deck
class CreateHeroSerializer(serializers.ModelSerializer):
class Meta:
model = Hero
fields = (
"type",
"idle_img",
"attack_img",
"die_img",
"health",
"attack",
"speed",
)
class GetHeroSerializer(serializers.ModelSerializer):
class Meta:
model = Hero
fields = (
"added",
"type",
"idle_img",
"attack_img",
"die_img",
"health",
"attack",
"speed",
)
class ListHeroSerializer(serializers.ModelSerializer):
class Meta:
model = Hero
fields = (
"uuid",
"type",
"idle_img",
"attack_img",
"die_img",
"health",
"attack",
"speed",
)
class CreatePlayerSerializer(serializers.ModelSerializer):
class Meta:
model = Player
fields = ("ton_wallet", "name")
class DeckCreateSerializer(serializers.ModelSerializer):
hero_ids = serializers.ListSerializer(
child=serializers.UUIDField(), min_length=16, max_length=16
)
class Meta:
model = Deck
fields = ("hero_ids",)
def validate_hero_ids(self, value):
for x in value:
if not (hero := Hero.objects.filter(uuid=x)):
raise ValidationError(f"Hero with uuid {x} doesn't exist")
if deck := HeroInDeck.objects.filter(hero=hero.first()):
raise ValidationError(
f"Hero with uuid {x} is already in deck with id {deck.first().deck.id}"
)
return value
def create(self, validated_data):
deck = Deck.objects.create(player=self.context["request"].user)
for x in validated_data["hero_ids"]:
HeroInDeck.objects.create(hero_id=x, deck=deck)
return deck
class GetDeckSerializer(serializers.ModelSerializer):
heroes = ListHeroSerializer(many=True)
class Meta:
model = Deck
fields = ("player_uuid", "heroes")
def get_heroes(self, val):
print(val)

View File

@ -1,16 +1,29 @@
from rest_framework import status
from rest_framework.generics import GenericAPIView, UpdateAPIView
from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, ListModelMixin
from rest_framework.mixins import (
CreateModelMixin,
RetrieveModelMixin,
ListModelMixin,
DestroyModelMixin,
UpdateModelMixin,
)
from rest_framework.response import Response
from game.authentication import PlayerAuthentication
from game.models import Hero
from game.serializers import CreateHeroSerializer, GetHeroSerializer, CreatePlayerView
from game.api.v1.serializers import (
CreateHeroSerializer,
GetHeroSerializer,
CreatePlayerSerializer,
ListHeroSerializer,
DeckCreateSerializer,
GetDeckSerializer,
)
from game.services.jwt import sign_jwt
class CreateHeroView(GenericAPIView, CreateModelMixin, ListModelMixin):
class ListCreateHeroView(GenericAPIView, CreateModelMixin, ListModelMixin):
authentication_classes = (PlayerAuthentication,)
def perform_create(self, serializer):
@ -27,7 +40,7 @@ class CreateHeroView(GenericAPIView, CreateModelMixin, ListModelMixin):
def get_serializer_class(self):
if self.request.method == "GET":
return GetHeroSerializer
return ListHeroSerializer
else:
return CreateHeroSerializer
@ -52,7 +65,7 @@ class RetrieveHeroView(RetrieveModelMixin, UpdateAPIView, GenericAPIView):
class PlayerCreateView(GenericAPIView, CreateModelMixin):
serializer_class = CreatePlayerView
serializer_class = CreatePlayerSerializer
def perform_create(self, serializer):
return serializer.save()
@ -65,4 +78,35 @@ class PlayerCreateView(GenericAPIView, CreateModelMixin):
# TODO: add JTI to refresh token
access_jwt = sign_jwt({"id": instance.id, "type": "access"}, t_life=3600)
refresh_jwt = sign_jwt({"id": instance.id, "type": "refresh"})
return Response({"access_token": access_jwt, "refresh_token": refresh_jwt}, status=status.HTTP_201_CREATED)
return Response(
{"access_token": access_jwt, "refresh_token": refresh_jwt},
status=status.HTTP_201_CREATED,
)
class DeckCreateView(GenericAPIView, CreateModelMixin):
serializer_class = DeckCreateSerializer
authentication_classes = (PlayerAuthentication,)
def perform_create(self, serializer):
return serializer.save(player=self.request.user)
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
instance = self.perform_create(serializer)
heroes_list = ListHeroSerializer(instance.get_heroes(), many=True)
return Response(heroes_list.data, status=status.HTTP_201_CREATED)
class RetireUpdateDeleteDeckView(
RetrieveHeroView, DestroyModelMixin, UpdateModelMixin, GenericAPIView
):
lookup_field = "pk"
def get_serializer_class(self):
if self.request.method == "GET":
return GetDeckSerializer
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)

View File

@ -7,7 +7,9 @@ from .services.jwt import read_jwt
class PlayerAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
if "Authorization" not in request.headers or not (token := request.headers["Authorization"]):
if "Authorization" not in request.headers or not (
token := request.headers["Authorization"]
):
raise exceptions.AuthenticationFailed("No credentials provided.")
t = read_jwt(token)

View File

@ -0,0 +1,57 @@
# Generated by Django 4.0.5 on 2022-06-04 14:16
import django.core.validators
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Hero',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
('added', models.DateTimeField(auto_now_add=True)),
('type', models.CharField(choices=[('WIZARD', 'wizard'), ('ARCHER', 'archer'), ('WARRIOR', 'warrior')], max_length=7)),
('idle_img', models.ImageField(upload_to='uploads/idle')),
('attack_img', models.ImageField(upload_to='uploads/attack')),
('die_img', models.ImageField(upload_to='uploads/die')),
('health', models.IntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10)])),
('speed', models.IntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10)])),
],
options={
'verbose_name': 'hero',
'verbose_name_plural': 'heroes',
'ordering': ['-added'],
},
),
migrations.CreateModel(
name='Player',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ton_wallet', models.CharField(max_length=50, verbose_name='TON wallet')),
('name', models.CharField(blank=True, max_length=100)),
('added', models.DateTimeField(auto_now_add=True)),
],
options={
'verbose_name': 'player',
'verbose_name_plural': 'players',
'ordering': ['-added'],
},
),
migrations.AddIndex(
model_name='player',
index=models.Index(fields=['ton_wallet'], name='game_player_ton_wal_47dd93_idx'),
),
migrations.AddIndex(
model_name='hero',
index=models.Index(fields=['uuid'], name='game_hero_uuid_ada5d9_idx'),
),
]

View File

@ -41,7 +41,10 @@ class Player(models.Model):
hero.player = self
hero.type = t
with open("/home/sanspie/Projects/chess_rpg_backend/media/dummy.jpg", "rb+") as file:
# TODO: create image pool to generate heroes (awaiting for designer)
with open(
"/home/sanspie/Projects/chess_rpg_backend/media/dummy.jpg", "rb+"
) as file:
hero.idle_img = File(file, name="dummy.jpg")
hero.attack_img = File(file, name="dummy.jpg")
hero.die_img = File(file, name="dummy.jpg")
@ -58,6 +61,8 @@ class Player(models.Model):
class Meta:
indexes = [models.Index(fields=["ton_wallet"])]
ordering = ["-created"]
db_table = "player"
verbose_name = "player"
verbose_name_plural = "players"
@ -65,7 +70,9 @@ class Player(models.Model):
class Hero(models.Model):
"""Model to store heroes and their stats, connected to player"""
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
uuid = models.UUIDField(
default=uuid.uuid4, editable=False, unique=True, primary_key=True
)
player = models.ForeignKey(
Player,
on_delete=models.CASCADE,
@ -94,5 +101,47 @@ class Hero(models.Model):
class Meta:
indexes = [models.Index(fields=["uuid"])]
ordering = ["-added"]
db_table = "hero"
verbose_name = "hero"
verbose_name_plural = "heroes"
class Deck(models.Model):
player = models.ForeignKey(
Player,
on_delete=models.CASCADE,
related_name="decks",
related_query_name="deck",
)
def __str__(self):
return f"{self.player.name} deck"
def get_heroes(self):
return [x.hero for x in HeroInDeck.objects.filter(deck=self)]
class Meta:
db_table = "deck"
verbose_name = "deck"
verbose_name_plural = "decks"
class HeroInDeck(models.Model):
deck = models.ForeignKey(
Deck,
on_delete=models.CASCADE,
related_name="hero_in_deck",
related_query_name="heroes",
)
hero = models.OneToOneField(
Hero,
on_delete=models.CASCADE,
related_name="hero_in_deck",
related_query_name="decks",
)
class Meta:
db_table = "hero_in_deck"
verbose_name = "Hero in deck"
verbose_name_plural = "Heroes in decks"

View File

@ -1,38 +0,0 @@
from rest_framework import serializers
from game.models import Hero, Player
class CreateHeroSerializer(serializers.ModelSerializer):
class Meta:
model = Hero
fields = (
"type",
"idle_img",
"attack_img",
"die_img",
"health",
"attack",
"speed",
)
class GetHeroSerializer(serializers.ModelSerializer):
class Meta:
model = Hero
fields = (
"added",
"type",
"idle_img",
"attack_img",
"die_img",
"health",
"attack",
"speed",
)
class CreatePlayerView(serializers.ModelSerializer):
class Meta:
model = Player
fields = ("ton_wallet", "name")

View File

@ -1,9 +1,19 @@
from django.urls import path
from game.views import CreateHeroView, RetrieveHeroView, PlayerCreateView
from game.api.v1.views import (
ListCreateHeroView,
RetrieveHeroView,
PlayerCreateView,
DeckCreateView,
RetireUpdateDeleteDeckView,
)
urlpatterns = [
path("hero/", CreateHeroView.as_view(), name="hero_api_create"),
path("hero/<uuid:uuid>", RetrieveHeroView.as_view(), name="hero_api_retrieve"),
path("player/", PlayerCreateView.as_view(), name="player_create_api"),
path("v1/hero/", ListCreateHeroView.as_view(), name="hero_api_create"),
path("v1/hero/<uuid:uuid>", RetrieveHeroView.as_view(), name="hero_api_retrieve"),
path("v1/player/", PlayerCreateView.as_view(), name="player_create_api"),
path("v1/deck/", DeckCreateView.as_view(), name="deck_create_api"),
path(
"v1/deck/<int:pk>", RetireUpdateDeleteDeckView.as_view(), name="deck_retire_api"
),
]