Merge branch 'backend' into 'master'

Backend

See merge request dm1sh/hackathon2020_komap!7
This commit is contained in:
Dmitriy Shishkov 2020-11-29 04:35:37 +05:00
commit df69c05ff5
11 changed files with 267 additions and 36 deletions

View File

@ -125,15 +125,15 @@ AUTH_PASSWORD_VALIDATORS = [
# Internationalization
# https://docs.djangoproject.com/en/3.1/topics/i18n/
LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'ru-RU'
TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Yekaterinburg'
USE_I18N = True
USE_L10N = True
USE_TZ = True
USE_TZ = False
# Static files (CSS, JavaScript, Images)
@ -145,6 +145,7 @@ STATIC_ROOT = BASE_DIR / 'static'
MEDIA_ROOT = BASE_DIR / 'media'
MEDIA_URL = '/media/'
MAX_CHECK_RADIUS = 1
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'

View File

@ -7,4 +7,5 @@ admin.site.register(models.Game)
admin.site.register(models.InvitationToken)
admin.site.register(models.Gamer)
admin.site.register(models.Team)
admin.site.register(models.CurrentCheckPoint)
# Register your models here.

View File

@ -0,0 +1,27 @@
# Generated by Django 3.1.3 on 2020-11-29 02:26
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('back', '0006_invitationtoken'),
]
operations = [
migrations.AlterField(
model_name='team',
name='game',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='teams', to='back.game'),
),
migrations.CreateModel(
name='CurrentCheckPoint',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('check_point', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='back.checkpoint')),
('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='back.team')),
],
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 3.1.3 on 2020-11-29 02:30
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('back', '0007_auto_20201129_0226'),
]
operations = [
migrations.AlterField(
model_name='checkpoint',
name='next_checkpoint_id',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='back.checkpoint'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1.3 on 2020-11-29 02:31
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('back', '0008_auto_20201129_0230'),
]
operations = [
migrations.RenameField(
model_name='checkpoint',
old_name='next_checkpoint_id',
new_name='next_checkpoint',
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 3.1.3 on 2020-11-29 02:39
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('back', '0009_auto_20201129_0231'),
]
operations = [
migrations.AddField(
model_name='checkpoint',
name='game',
field=models.ForeignKey(default=2, on_delete=django.db.models.deletion.CASCADE, to='back.game'),
preserve_default=False,
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 3.1.3 on 2020-11-29 04:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('back', '0010_checkpoint_game'),
]
operations = [
migrations.AddField(
model_name='game',
name='points',
field=models.DecimalField(decimal_places=0, default=50, max_digits=3),
preserve_default=False,
),
]

View File

@ -23,6 +23,7 @@ class Game(models.Model):
co_op = models.BooleanField(blank = False, null = False)
max_gamers = models.DecimalField(max_digits = 3, decimal_places = 0, blank = False, null = False)
active = models.BooleanField(default = True)
points = models.DecimalField(max_digits = 3, decimal_places = 0, blank = False, null = False)
slug = models.SlugField(unique = True, blank = True, null = False)
class CheckPoint(models.Model):
@ -31,14 +32,15 @@ class CheckPoint(models.Model):
coordinates_lat = models.DecimalField(max_digits = 8, decimal_places = 6, blank = True, null = True)
coordinates_lon = models.DecimalField(max_digits = 8, decimal_places = 6, blank = True, null = True)
description = models.TextField(blank = False, null = False)
next_checkpoint_id = models.DecimalField(max_digits = 4, decimal_places = 0, blank = True, null = True)
next_checkpoint = models.ForeignKey('self', on_delete = models.CASCADE, blank = True, null = True)
qr_data = models.TextField(blank = True, null = True)
last = models.BooleanField(default = False)
start = models.BooleanField(default = False)
address = models.CharField(max_length = 200, blank = True, null = True)
game = models.ForeignKey(Game, on_delete = models.CASCADE, blank = False, null = False)
class Team(models.Model):
game = models.ForeignKey(Game, on_delete= models.CASCADE, blank = False, null = False)
game = models.ForeignKey(Game, on_delete= models.CASCADE,related_name = 'teams', blank = False, null = False)
active = models.BooleanField(default = False)
finished = models.BooleanField(default = False)
start = models.DateTimeField(blank = True, null = True)
@ -53,6 +55,10 @@ 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)
class CurrentCheckPoint(models.Model):
team = models.ForeignKey(Team, on_delete = models.CASCADE, blank = False, null = False)
check_point = models.ForeignKey(CheckPoint, on_delete = models.CASCADE, 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, Team, Gamer
from .models import Game, Team, Gamer, CheckPoint
class GameListSerializer(serializers.ModelSerializer):
class Meta:
@ -17,6 +17,30 @@ class UserRegisterSerializer(serializers.ModelSerializer):
'email'
]
class CheckPointCoordinatesSerializer(serializers.ModelSerializer):
class Meta:
model = CheckPoint
fields = [
'coordinates_lon',
'coordinates_lat',
'start',
'last'
]
class CurrentCheckPointSerializer(serializers.ModelSerializer):
class Meta:
model = CheckPoint
fields = [
'check_type',
'name',
'description',
'address',
]
# def get_done_check_points(self, obj):
# return(CheckPointCoordinatesSerializer(obj).data)
class TeamSerializer(serializers.ModelSerializer):
game = serializers.SerializerMethodField()
gamers = serializers.SerializerMethodField()

View File

@ -3,9 +3,11 @@ from rest_framework.authtoken.views import obtain_auth_token
from . import views
urlpatterns = [
path('games', views.ListGamesAPIView.as_view(), name = 'ListGames'),
path('games/<visited>', views.ListGamesAPIView.as_view(), name = 'ListGames'),
path('game/<slug>', views.SingleGameAPIView, name = 'SingleGame'),
path('game/<slug>/take_part',views.GameTakePartAPIView, name = 'GameTakePartAPIView'),
path('game/<slug>/start', views.StartGameAPIView, name = 'StartGame'),
path('game/<slug>/close_check_point', views.CloseCheckPointAPIView, name = 'CloseCheckPoint'),
path('game/<slug>/join_team/<token>',views.JoinTeamAPIView, name = 'JoinTeam'),
path('register', views.UserRegisterAPIView, name = 'UserRegister'),
path('login', obtain_auth_token, name = 'UserLogin'),

View File

@ -10,29 +10,33 @@ 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, TeamSerializer
from .serializers import UserRegisterSerializer, UserResetPasswordSerializer, UserPasswordSerializer, GameListSerializer, TeamSerializer, CurrentCheckPointSerializer, CheckPointCoordinatesSerializer
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, Team, Gamer, Profile, InvitationToken
from .models import Game, Team, Gamer, Profile, InvitationToken, CurrentCheckPoint, CheckPoint
from django.conf import settings
from django.utils import timezone
from hashlib import sha1
import six
import threading
import math
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()
def list_to_queryset(model_list):
if len(model_list) > 0:
return model_list[0].__class__.objects.filter(
pk__in=[obj.pk for obj in model_list])
else:
return []
class ListGamesAPIView(generics.ListAPIView):
serializer_class = GameListSerializer
permission_classes = [permissions.IsAuthenticated]
@ -42,7 +46,15 @@ class ListGamesAPIView(generics.ListAPIView):
search_fields = ['name', 'description']
def get_queryset(self):
return Game.objects.filter(active = True)
profile = Profile.objects.get(user = self.request.user)
if(self.kwargs['visited'] == '0'):
data = list(map(lambda team_i: team_i.game,filter(lambda ob:not Gamer.objects.filter(profile = profile, team = ob).exists(), Team.objects.all())))
else:
data = list(map(lambda team_i: team_i.game,filter(lambda ob:Gamer.objects.filter(profile = profile, team = ob).exists(), Team.objects.all())))
if(len(data)!=0):
return list_to_queryset(data)
else:
return Game.objects.none()
@api_view(['GET'])
@permission_classes([permissions.IsAuthenticated])
@ -63,19 +75,76 @@ def SingleGameAPIView(request, slug):
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)
if(not team.active):
if(team.captain == profile and (not game.co_op or len(gamers) == game.max_gamers) and not team.finished):
can_start = True
if(team.captain == profile and len(gamers) != game.max_gamers and not team.finished):
invite = InvitationToken.objects.filter(team = team)
if(invite.exists()):
invite = invite.first()
invite_token = invite.token
if(team.finished):
done_chepoints = []
cur_checkpoint = CurrentCheckPoint.objects.get(team = team).check_point
temp_check_point = CheckPoint.objects.get(game = team.game, start = True)
done_chepoints.append(CheckPointCoordinatesSerializer(temp_check_point).data)
while temp_check_point.id!=cur_checkpoint.id:
temp_check_point = temp_check_point.next_checkpoint
done_chepoints.append(CheckPointCoordinatesSerializer(temp_check_point).data)
return Response({'ok':True, 'can_take_part':False, 'can_start':can_start, 'invite_token':invite_token, **data, 'done_chepoints':done_chepoints}, status = status.HTTP_200_OK)
return Response({'ok':True, 'can_take_part':False, 'can_start':can_start, 'invite_token':invite_token, **data}, status = status.HTTP_200_OK)
else:
done_chepoints = []
cur_checkpoint = CurrentCheckPoint.objects.get(team = team).check_point
cur_checkpoint_serializer = CurrentCheckPointSerializer(cur_checkpoint)
temp_check_point = CheckPoint.objects.get(game = team.game, start = True)
done_chepoints.append(CheckPointCoordinatesSerializer(temp_check_point).data)
while temp_check_point.id!=cur_checkpoint.id:
temp_check_point = temp_check_point.next_checkpoint
done_chepoints.append(CheckPointCoordinatesSerializer(temp_check_point).data)
return Response({'ok':True, 'can_take_part':False, 'can_start':can_start, 'invite_token':invite_token, **data, 'current_checkpoint':cur_checkpoint_serializer.data, 'done_chepoints':done_chepoints}, 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)
return Response({'ok':False, 'error':'Game does not exist'}, status = status.HTTP_400_BAD_REQUEST)
@api_view(['POST'])
@permission_classes([permissions.IsAuthenticated])
def CloseCheckPointAPIView(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):
temp_check_point = CheckPointCoordinatesSerializer(data = request.data)
if(temp_check_point.is_valid() and team.active and not team.finished):
temp_check_point = temp_check_point.validated_data
cur_checkpoint = CurrentCheckPoint.objects.get(team = team)
dest = math.sqrt((temp_check_point['coordinates_lat']-cur_checkpoint.check_point.next_checkpoint.coordinates_lat)**2 + (temp_check_point['coordinates_lon']-cur_checkpoint.check_point.next_checkpoint.coordinates_lon)**2)
if(dest<settings.MAX_CHECK_RADIUS):
cur_checkpoint.check_point = cur_checkpoint.check_point.next_checkpoint
cur_checkpoint.save()
if(cur_checkpoint.check_point.last):
team.active = False
team.finished = True
team.end = timezone.now()
team.save()
for gamer in team.gamers.all():
temp_prof = gamer.profile
temp_prof.points += game.points
temp_prof.save()
return Response({'ok':True}, status = status.HTTP_200_OK)
return Response({'ok':False, 'error':'You are too far from the place'}, status = status.HTTP_400_BAD_REQUEST)
return Response({'ok':False, 'error':temp_check_point.errors if temp_check_point.errors else 'Game finished' }, status = status.HTTP_400_BAD_REQUEST)
return Response({'ok':False, 'error':'You do not take part in 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])
@ -84,7 +153,7 @@ def GameTakePartAPIView(request, slug):
if(game.exists()):
game = game.first()
profile = Profile.objects.get(user = request.user)
teams = Team.objects.filter(game = game, captain = profile)
teams = Team.objects.filter(game = game)
team = []
for team_i in teams:
if(Gamer.objects.filter(profile = profile, team = team_i).exists()):
@ -97,8 +166,8 @@ def GameTakePartAPIView(request, slug):
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)
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])
@ -115,9 +184,34 @@ def JoinTeamAPIView(request, slug, token):
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)
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])
def StartGameAPIView(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(team):
if(team.captain == profile and not team.finished and not team.active):
team.active = True
team.start = timezone.now()
team.save()
first_check_point = CheckPoint.objects.get(game = team.game, start = True)
cur_checkpoint = CurrentCheckPoint.objects.create(team = team, check_point = first_check_point)
return Response({'ok':True}, status = status.HTTP_200_OK)
return Response({'ok':False, 'error':'You are not a captain or game finished or game in progress'}, status = status.HTTP_400_BAD_REQUEST)
return Response({'ok':False, 'error':'You do not take part in 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])
@ -130,7 +224,7 @@ def UserRegisterAPIView(request):
try:
validate_password(data['password'])
except Exception as errors:
return Response({'ok':False, 'error':errors} ,status = status.HTTP_400_BAD_REQUEST)
return Response({'ok':False, 'error':errors}, status = status.HTTP_400_BAD_REQUEST)
user = User.objects.create(username = data['username'], email = data['email'], is_active = False)
user.set_password(data['password'])
user.save()
@ -145,7 +239,7 @@ def UserRegisterAPIView(request):
message_task = threading.Thread(target = async_email_send, args=( mail_subject, message, [data['email']] ))
message_task.start()
return Response({'ok':True}, status = status.HTTP_200_OK)
return Response({'ok':False, 'error':serializer.errors},status = status.HTTP_400_BAD_REQUEST)
return Response({'ok':False, 'error':serializer.errors}, status = status.HTTP_400_BAD_REQUEST)
@api_view(['POST'])
@permission_classes([permissions.AllowAny])
@ -192,7 +286,7 @@ def UserResetPasswordAPIView(request, uidb64, token):
try:
validate_password(data['password'])
except Exception as errors:
return Response({'ok':False, 'error':errors} ,status = status.HTTP_400_BAD_REQUEST)
return Response({'ok':False, 'error':errors}, status = status.HTTP_400_BAD_REQUEST)
uid = force_text(urlsafe_base64_decode(uidb64))
user = User.objects.filter(id = uid)
if(user.exists()):
@ -201,8 +295,8 @@ def UserResetPasswordAPIView(request, uidb64, token):
user.set_password(data['password'])
user.save()
return Response({'ok':True}, status = status.HTTP_200_OK)
return Response({'ok':False, 'error':'Invalid password reset link'} ,status = status.HTTP_400_BAD_REQUEST)
return Response({'ok':False, 'error':'User does not exist'} ,status = status.HTTP_400_BAD_REQUEST)
return Response({'ok':False, 'error':'Invalid password reset link'}, status = status.HTTP_400_BAD_REQUEST)
return Response({'ok':False, 'error':'User does not exist'}, status = status.HTTP_400_BAD_REQUEST)
return Response({'ok':False, 'error':serializer.errors}, status = status.HTTP_400_BAD_REQUEST)
@api_view(['POST'])