diff --git a/backend/Ugra_hackaton/settings.py b/backend/Ugra_hackaton/settings.py index 75fd41e..5649a97 100644 --- a/backend/Ugra_hackaton/settings.py +++ b/backend/Ugra_hackaton/settings.py @@ -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' diff --git a/backend/back/admin.py b/backend/back/admin.py index abbbb6f..a02d51d 100644 --- a/backend/back/admin.py +++ b/backend/back/admin.py @@ -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. diff --git a/backend/back/migrations/0007_auto_20201129_0226.py b/backend/back/migrations/0007_auto_20201129_0226.py new file mode 100644 index 0000000..af3463b --- /dev/null +++ b/backend/back/migrations/0007_auto_20201129_0226.py @@ -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')), + ], + ), + ] diff --git a/backend/back/migrations/0008_auto_20201129_0230.py b/backend/back/migrations/0008_auto_20201129_0230.py new file mode 100644 index 0000000..b0f7d20 --- /dev/null +++ b/backend/back/migrations/0008_auto_20201129_0230.py @@ -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'), + ), + ] diff --git a/backend/back/migrations/0009_auto_20201129_0231.py b/backend/back/migrations/0009_auto_20201129_0231.py new file mode 100644 index 0000000..79f2fd0 --- /dev/null +++ b/backend/back/migrations/0009_auto_20201129_0231.py @@ -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', + ), + ] diff --git a/backend/back/migrations/0010_checkpoint_game.py b/backend/back/migrations/0010_checkpoint_game.py new file mode 100644 index 0000000..13546c7 --- /dev/null +++ b/backend/back/migrations/0010_checkpoint_game.py @@ -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, + ), + ] diff --git a/backend/back/migrations/0011_game_points.py b/backend/back/migrations/0011_game_points.py new file mode 100644 index 0000000..b778d43 --- /dev/null +++ b/backend/back/migrations/0011_game_points.py @@ -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, + ), + ] diff --git a/backend/back/models.py b/backend/back/models.py index 71ac7ad..0120073 100644 --- a/backend/back/models.py +++ b/backend/back/models.py @@ -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): diff --git a/backend/back/serializers.py b/backend/back/serializers.py index 968e9f8..c36bc79 100644 --- a/backend/back/serializers.py +++ b/backend/back/serializers.py @@ -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() diff --git a/backend/back/urls.py b/backend/back/urls.py index 89bfe90..5245096 100644 --- a/backend/back/urls.py +++ b/backend/back/urls.py @@ -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/', views.ListGamesAPIView.as_view(), name = 'ListGames'), path('game/', views.SingleGameAPIView, name = 'SingleGame'), path('game//take_part',views.GameTakePartAPIView, name = 'GameTakePartAPIView'), + path('game//start', views.StartGameAPIView, name = 'StartGame'), + path('game//close_check_point', views.CloseCheckPointAPIView, name = 'CloseCheckPoint'), path('game//join_team/',views.JoinTeamAPIView, name = 'JoinTeam'), path('register', views.UserRegisterAPIView, name = 'UserRegister'), path('login', obtain_auth_token, name = 'UserLogin'), diff --git a/backend/back/views.py b/backend/back/views.py index 88f6e99..2a34e5a 100644 --- a/backend/back/views.py +++ b/backend/back/views.py @@ -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