Merge branch 'backend' into 'master'

add Game list, single game display, invites to teams

See merge request dm1sh/hackathon2020_komap!6
This commit is contained in:
Dmitriy Shishkov 2020-11-28 23:50:24 +05:00
commit d67f05d7c2
9 changed files with 268 additions and 10 deletions

View File

@ -4,4 +4,7 @@ from . import models
admin.site.register(models.Profile)
admin.site.register(models.CheckPoint)
admin.site.register(models.Game)
admin.site.register(models.InvitationToken)
admin.site.register(models.Gamer)
admin.site.register(models.Team)
# Register your models here.

View File

@ -0,0 +1,50 @@
# Generated by Django 3.1.3 on 2020-11-28 16:20
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('back', '0003_auto_20201128_1939'),
]
operations = [
migrations.CreateModel(
name='Game_in_progress',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('active', models.BooleanField(default=False)),
('finished', models.BooleanField(default=False)),
('start', models.DateTimeField(blank=True, null=True)),
('end', models.DateTimeField(blank=True, null=True)),
],
),
migrations.AlterField(
model_name='game',
name='slug',
field=models.SlugField(blank=True, unique=True),
),
migrations.CreateModel(
name='Team',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('captain', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='back.profile')),
('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='back.game_in_progress')),
],
),
migrations.CreateModel(
name='Gamer',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='back.profile')),
('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='gamers', to='back.team')),
],
),
migrations.AddField(
model_name='game_in_progress',
name='game',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='back.game'),
),
]

View File

@ -0,0 +1,42 @@
# Generated by Django 3.1.3 on 2020-11-28 17:16
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('back', '0004_auto_20201128_2120'),
]
operations = [
migrations.AddField(
model_name='team',
name='active',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='team',
name='end',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name='team',
name='finished',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='team',
name='start',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AlterField(
model_name='team',
name='game',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='back.game'),
),
migrations.DeleteModel(
name='Game_in_progress',
),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 3.1.3 on 2020-11-28 18:17
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('back', '0005_auto_20201128_2216'),
]
operations = [
migrations.CreateModel(
name='InvitationToken',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('token', models.CharField(max_length=200)),
('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='back.team')),
],
),
]

View File

@ -37,6 +37,22 @@ class CheckPoint(models.Model):
start = models.BooleanField(default = False)
address = models.CharField(max_length = 200, blank = True, null = True)
class Team(models.Model):
game = models.ForeignKey(Game, on_delete= models.CASCADE, blank = False, null = False)
active = models.BooleanField(default = False)
finished = models.BooleanField(default = False)
start = models.DateTimeField(blank = True, null = True)
end = models.DateTimeField(blank = True, null = True)
captain = models.ForeignKey(Profile, on_delete = models.CASCADE, blank = True, null = True)
class InvitationToken(models.Model):
token = models.CharField(max_length = 200, blank = False, null = False)
team = models.ForeignKey(Team, on_delete = models.CASCADE, blank = False, null = False)
class Gamer(models.Model):
profile = models.ForeignKey(Profile, on_delete = models.CASCADE, blank = False, null = False)
team = models.ForeignKey(Team, on_delete = models.CASCADE, related_name = 'gamers', blank = False, null = False)
@receiver(post_save, sender = User)
def create_profile(sender, instance, created, **kwargs):
if(created):

View File

@ -1,6 +1,6 @@
from rest_framework import serializers
from django.contrib.auth.models import User
from .models import Game
from .models import Game, Team, Gamer
class GameListSerializer(serializers.ModelSerializer):
class Meta:
@ -17,6 +17,28 @@ class UserRegisterSerializer(serializers.ModelSerializer):
'email'
]
class TeamSerializer(serializers.ModelSerializer):
game = serializers.SerializerMethodField()
gamers = serializers.SerializerMethodField()
captain = serializers.SerializerMethodField()
class Meta:
model = Team
fields = [
'active',
'finished',
'start',
'end',
'captain',
'game',
'gamers',
]
def get_game(self, obj):
return(GameListSerializer(obj.game).data)
def get_gamers(self, obj):
return(list(map(lambda ob:ob.profile.user.username,Gamer.objects.filter(team = obj))))
def get_captain(self, obj):
return(obj.captain.user.username)
class UserResetPasswordSerializer(serializers.ModelSerializer):
email = serializers.EmailField(required=True)
class Meta:

View File

@ -4,8 +4,9 @@ from . import views
urlpatterns = [
path('games', views.ListGamesAPIView.as_view(), name = 'ListGames'),
path('game/<slug>', views.SingleGameAPIView.as_view(), name = 'SingleGame'),
# path('game/<slug>/take_part',),
path('game/<slug>', views.SingleGameAPIView, name = 'SingleGame'),
path('game/<slug>/take_part',views.GameTakePartAPIView, name = 'GameTakePartAPIView'),
path('game/<slug>/join_team/<token>',views.JoinTeamAPIView, name = 'JoinTeam'),
path('register', views.UserRegisterAPIView, name = 'UserRegister'),
path('login', obtain_auth_token, name = 'UserLogin'),
path('logout', views.UserLogoutAPIView, name = 'UserLogout'),

View File

@ -10,12 +10,13 @@ from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.template.loader import render_to_string
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.core.mail import EmailMultiAlternatives
from .serializers import UserRegisterSerializer, UserResetPasswordSerializer, UserPasswordSerializer, GameListSerializer
from .serializers import UserRegisterSerializer, UserResetPasswordSerializer, UserPasswordSerializer, GameListSerializer, TeamSerializer
from django.utils.html import strip_tags
from django.contrib.auth.password_validation import validate_password
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import OrderingFilter, SearchFilter
from .models import Game
from .models import Game, Team, Gamer, Profile, InvitationToken
from hashlib import sha1
import six
import threading
@ -23,6 +24,12 @@ class EmailTokenGenerator(PasswordResetTokenGenerator):
def _make_hash_value(self, user, timestamp):
return(six.text_type(user.pk) + six.text_type(timestamp) + six.text_type(user.is_active))
# class InviteTokenGenerator(PasswordResetTokenGenerator):
# def make_token(self, user,):
# return self._make_token_with_timestamp(user, self._num_seconds(self._now()))
# def _make_hash_value(self, user, timestamp):
# return(six.text_type(user.pk) + six.text_type(timestamp) + six.text_type(user.is_active))
email_token_gen = EmailTokenGenerator()
password_token_gen = PasswordResetTokenGenerator()
@ -37,11 +44,80 @@ class ListGamesAPIView(generics.ListAPIView):
def get_queryset(self):
return Game.objects.filter(active = True)
class SingleGameAPIView(generics.RetrieveAPIView):
serializer_class = GameListSerializer
permission_classes = [permissions.IsAuthenticated]
lookup_field = 'slug'
queryset = Game.objects.all()
@api_view(['GET'])
@permission_classes([permissions.IsAuthenticated])
def SingleGameAPIView(request, slug):
game = Game.objects.filter(slug = slug)
if(game.exists()):
profile = Profile.objects.get(user = request.user)
game = game.first()
teams = Team.objects.filter(game = game)
team = []
for team_i in teams:
if(Gamer.objects.filter(profile = profile, team = team_i).exists()):
team = team_i
break
if(team):
serializer = TeamSerializer(team)
data = serializer.data
gamers = data['gamers']
can_start = False
invite_token = ''
if(team.captain == profile and (not game.co_op or len(gamers) == game.max_gamers)):
can_start = True
if(team.captain == profile):
invite = InvitationToken.objects.filter(team = team)
if(invite.exists()):
invite = invite.first()
invite_token = invite.token
return Response({'ok':True, 'can_take_part':False, 'can_start':can_start, 'invite_token':invite_token, **data}, status = status.HTTP_200_OK)
else:
serializer = GameListSerializer(game)
data = serializer.data
return Response({'ok':True, 'can_take_part':True, **serializer.data}, status = status.HTTP_200_OK)
return Response({'ok':False, 'error':'Game does not exist'},status = status.HTTP_400_BAD_REQUEST)
@api_view(['POST'])
@permission_classes([permissions.IsAuthenticated])
def GameTakePartAPIView(request, slug):
game = Game.objects.filter(slug = slug)
if(game.exists()):
game = game.first()
profile = Profile.objects.get(user = request.user)
teams = Team.objects.filter(game = game, captain = profile)
team = []
for team_i in teams:
if(Gamer.objects.filter(profile = profile, team = team_i).exists()):
team = team_i
break
if(not team):
team = Team.objects.create(game = game, captain = profile)
if(game.co_op):
invite_token = sha1(bytes(f'{team.id}{game.id}{profile.id}{profile.user.username}','utf-8')).hexdigest()[:200]
InvitationToken.objects.create(team = team, token = invite_token)
gamer = Gamer.objects.create(team = team, profile = profile)
return Response({'ok':True}, status = status.HTTP_200_OK)
return Response({'ok':False, 'error':'You alredy take part in this game'},status = status.HTTP_400_BAD_REQUEST)
return Response({'ok':False, 'error':'Game does not exist'},status = status.HTTP_400_BAD_REQUEST)
@api_view(['POST'])
@permission_classes([permissions.IsAuthenticated])
def JoinTeamAPIView(request, slug, token):
game = Game.objects.filter(slug = slug)
if(game.exists()):
invite = InvitationToken.objects.filter(token = token)
if(invite.exists()):
profile = Profile.objects.get(user = request.user)
invite = invite.first()
team = invite.team
serializer = TeamSerializer(team)
gamers = serializer.data['gamers']
if(len(gamers) < team.game.max_gamers):
Gamer.objects.create(team = team, profile = profile)
return Response({'ok':True},status = status.HTTP_200_OK)
return Response({'ok':False, 'error':'Game finished or team is full'},status = status.HTTP_400_BAD_REQUEST)
return Response({'ok':False, 'error':'Invalid invite link'},status = status.HTTP_400_BAD_REQUEST)
return Response({'ok':False, 'error':'Game does not exist'},status = status.HTTP_400_BAD_REQUEST)
@api_view(['POST'])
@permission_classes([~permissions.IsAuthenticated])

26
backend/requirements.txt Normal file
View File

@ -0,0 +1,26 @@
asgiref==3.3.1
certifi==2020.11.8
cffi==1.14.4
chardet==3.0.4
cryptography==3.2.1
defusedxml==0.7.0rc1
Django==3.1.3
django-braces==1.14.0
django-filter==2.4.0
django-oauth-toolkit==1.3.3
django-rest-framework-social-oauth2==1.1.0
djangorestframework==3.12.2
idna==2.10
oauthlib==3.1.0
psycopg2==2.8.6
pycparser==2.20
PyJWT==1.7.1
python3-openid==3.2.0
pytz==2020.4
requests==2.25.0
requests-oauthlib==1.3.0
six==1.15.0
social-auth-app-django==4.0.0
social-auth-core==3.3.3
sqlparse==0.4.1
urllib3==1.26.2