Merge pull request #1 from edwilding/contribute_games

Added tonnes of features, lets see what breaks.
This commit is contained in:
edwilding 2019-11-26 01:14:57 +00:00 committed by GitHub
commit eb4922ba0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 500 additions and 102 deletions

View File

@ -7,8 +7,7 @@ from .models import User, Coord
@admin.register(Coord) @admin.register(Coord)
class CoordAdmin(admin.ModelAdmin): class CoordAdmin(admin.ModelAdmin):
list_display = 'id', 'country', list_display = 'id',
search_fields = 'country',
class CustomUserAdmin(UserAdmin): class CustomUserAdmin(UserAdmin):

View File

@ -5,26 +5,17 @@ from django.conf import settings
from django.forms import widgets from django.forms import widgets
from django.forms.utils import to_current_timezone from django.forms.utils import to_current_timezone
from django.contrib.auth.forms import UserCreationForm, UserChangeForm from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from django.forms import modelformset_factory
from geogame.main.models import ( from geogame.main.models import (
Coord, User, GameRound Coord, User, GameRound, Challenge
) )
from dal import autocomplete
#from django_starfield import Stars
class CoordForm(forms.ModelForm):
class ChallengeForm(forms.ModelForm):
class Meta: class Meta:
model = Coord model = Challenge
fields = ('lat', 'lng', 'country') fields = ('name',)
widgets = {'country': autocomplete.ModelSelect2(url='game:country-autocomplete')}
def clean(self):
cleaned_data = super(CoordForm, self).clean()
lat = cleaned_data.get('lat')
lng = cleaned_data.get('lng')
class GuessForm(forms.ModelForm): class GuessForm(forms.ModelForm):
@ -47,7 +38,7 @@ class APIForm(forms.ModelForm):
class Meta: class Meta:
model = User model = User
fields = ('api_key',) fields = ('api_key', 'display_name',)
class CustomUserCreationForm(UserCreationForm): class CustomUserCreationForm(UserCreationForm):
@ -62,3 +53,12 @@ class CustomUserChangeForm(UserChangeForm):
class Meta: class Meta:
model = User model = User
fields = ('username', 'email') fields = ('username', 'email')
ChallengeCoordFormSet = modelformset_factory(
Coord,
fields=('lat', 'lng',),
extra=1,
)

View File

@ -20,13 +20,18 @@ class Command(BaseCommand):
with transaction.atomic(): with transaction.atomic():
country = Country.objects.get(country='United Kingdom') country = Country.objects.get(country="United States of America")
user = User.objects.first() user = User.objects.first()
with open('/home/ubuntu/lat_lng.csv', 'r') as csvfile: with open('/home/ubuntu/lat_lng.csv', 'r') as csvfile:
datareader = csv.reader(csvfile) datareader = csv.reader(csvfile)
for row in datareader: for row in datareader:
if row: if row:
Coord.objects.create(lat=row[0], lng=row[1], country=country, user=user) _,c = Coord.objects.get_or_create(
lat=row[0],
lng=row[1],
country=country,
user=user
)
if options['dry_run']: if options['dry_run']:
transaction.set_rollback(True) transaction.set_rollback(True)

View File

@ -0,0 +1,64 @@
# Generated by Django 2.2.4 on 2019-11-25 23:44
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('main', '0003_auto_20190822_2204'),
]
operations = [
migrations.CreateModel(
name='Challenge',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, db_index=True, max_length=255, verbose_name='Challenge Name')),
('average', models.PositiveIntegerField()),
],
),
migrations.RemoveField(
model_name='coord',
name='country',
),
migrations.RemoveField(
model_name='game',
name='country',
),
migrations.AddField(
model_name='coord',
name='reports',
field=models.PositiveIntegerField(default=0),
),
migrations.AddField(
model_name='gameround',
name='result',
field=models.PositiveIntegerField(default=0),
),
migrations.AddField(
model_name='user',
name='display_name',
field=models.CharField(blank=True, db_index=True, max_length=25, verbose_name='display name'),
),
migrations.DeleteModel(
name='Country',
),
migrations.AddField(
model_name='challenge',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='challenge_user', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='coord',
name='challenge',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='coord_challenge', to='main.Challenge'),
),
migrations.AddField(
model_name='game',
name='challenge',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='game_challenge', to='main.Challenge'),
),
]

View File

@ -5,6 +5,7 @@ import uuid
from django.contrib.postgres.search import SearchVectorField, SearchQuery, SearchVector from django.contrib.postgres.search import SearchVectorField, SearchQuery, SearchVector
from django.conf import settings from django.conf import settings
from django.db import models, transaction, IntegrityError from django.db import models, transaction, IntegrityError
from django.db.models import Avg
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.conf import settings from django.conf import settings
@ -13,8 +14,6 @@ from django.shortcuts import reverse
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.utils import timezone from django.utils import timezone
from django_countries.fields import CountryField
from geopy import distance from geopy import distance
@ -23,17 +22,16 @@ class User(AbstractUser):
last_name = models.CharField(_('last name'), max_length=50, blank=True, db_index=True) last_name = models.CharField(_('last name'), max_length=50, blank=True, db_index=True)
email = models.EmailField(_('email address'), blank=False, null=False, db_index=True) email = models.EmailField(_('email address'), blank=False, null=False, db_index=True)
api_key = models.CharField(_('google maps api key'), max_length=255, blank=True, db_index=True) api_key = models.CharField(_('google maps api key'), max_length=255, blank=True, db_index=True)
display_name = models.CharField(_('display name'), max_length=25, blank=True, db_index=True)
def generate_new_game(self, country=None): def generate_new_game(self):
game = Game.objects.create( game = Game.objects.create(
start=timezone.now(), start=timezone.now(),
user=self, user=self,
score=0, score=0,
active=True, active=True,
) )
qs = Coord.objects.all() qs = Coord.objects.filter(reports=0)
if country:
qs = qs.filter(country=country)
coords = qs.order_by('?')[:5] coords = qs.order_by('?')[:5]
for i, coord in enumerate(coords): for i, coord in enumerate(coords):
round = GameRound.objects.create( round = GameRound.objects.create(
@ -52,34 +50,65 @@ class User(AbstractUser):
return Game.objects.filter(user=self).update(active=False) return Game.objects.filter(user=self).update(active=False)
class Country(models.Model): class Challenge(models.Model):
country = models.CharField(_('Country'), max_length=255, null=False, blank=False) name = models.CharField(_('Challenge Name'), max_length=255, blank=True, db_index=True)
average = models.PositiveIntegerField(default=0)
user = models.ForeignKey(User, models.PROTECT,
related_name='challenge_user',
null=False, blank=False)
def __str__(self): def update_average_score(self):
return self.country average = GameRound.objects.filter(game__challenge=self).aggregate(Avg('result')).get('result__avg', 0)
self.average = average
self.save()
return average
def setup_challenge(self, user):
rounds = Coord.objects.filter(challenge=self)
game = Game.objects.create(
challenge=self,
start=timezone.now(),
user=user,
score=0,
active=True,
)
for order, coord in enumerate(rounds):
round = GameRound.objects.create(
game=game,
coord=coord,
order=order
)
if order == 0:
round_id = round.id
return game.id, round_id
class Coord(models.Model): class Coord(models.Model):
lng = models.CharField(_('longitude'), max_length=50, null=False, blank=False) lng = models.CharField(_('longitude'), max_length=50, null=False, blank=False)
lat = models.CharField(_('latitude'), max_length=50, null=False, blank=False) lat = models.CharField(_('latitude'), max_length=50, null=False, blank=False)
country = models.ForeignKey(Country, models.PROTECT,
related_name='coord_country',
null=False, blank=False)
user = models.ForeignKey(User, models.PROTECT, user = models.ForeignKey(User, models.PROTECT,
related_name='coord_user', related_name='coord_user',
null=False, blank=False) null=False, blank=False)
reports = models.PositiveIntegerField(default=0)
challenge = models.ForeignKey(Challenge, models.PROTECT,
related_name='coord_challenge',
null=True, blank=True)
def report(self):
self.reports = self.reports + 1
self.save()
class Game(models.Model): class Game(models.Model):
challenge = models.ForeignKey(Challenge, models.PROTECT,
related_name='game_challenge',
null=True, blank=True)
start = models.DateTimeField() start = models.DateTimeField()
user = models.ForeignKey(User, models.PROTECT, user = models.ForeignKey(User, models.PROTECT,
related_name='game_user', related_name='game_user',
null=False, blank=False) null=False, blank=False)
score = models.PositiveIntegerField() score = models.PositiveIntegerField()
active = models.BooleanField() active = models.BooleanField()
country = models.ForeignKey(Country, models.PROTECT,
related_name='game_country',
null=True, blank=True)
def get_rounds(self): def get_rounds(self):
return GameRound.objects.filter(game=self) return GameRound.objects.filter(game=self)
@ -95,10 +124,18 @@ class GameRound(models.Model):
order = models.IntegerField(_('round order')) order = models.IntegerField(_('round order'))
guess_lat = models.CharField(_('latitude'), max_length=50, blank=True, null=True) guess_lat = models.CharField(_('latitude'), max_length=50, blank=True, null=True)
guess_lng = models.CharField(_('longitude'), max_length=50, blank=True, null=True) guess_lng = models.CharField(_('longitude'), max_length=50, blank=True, null=True)
result = models.PositiveIntegerField(default=0)
def get_distance(self): def get_distance(self):
if not self.guess_lat or not self.guess_lng: if not self.guess_lat or not self.guess_lng:
self.distance = 0
self.save()
return 0 return 0
actual_coord = (self.coord.lat, self.coord.lng,) actual_coord = (self.coord.lat, self.coord.lng,)
guess_coord = (self.guess_lat, self.guess_lng,) guess_coord = (self.guess_lat, self.guess_lng,)
return distance.distance(actual_coord, guess_coord).km
result = distance.distance(actual_coord, guess_coord).km
self.result = result
self.save()
return result

View File

@ -2,8 +2,8 @@ from django.conf.urls import url
from django.contrib.auth.decorators import permission_required from django.contrib.auth.decorators import permission_required
from geogame.main.views import ( from geogame.main.views import (
RoundView, RoundRecapView, GameRecapView, NewGameView, RoundView, RoundRecapView, GameRecapView, NewGameView, ChallengeListView,
ContributeView, CountryAutocomplete, RemoveCoordView EditChallengeView, RemoveCoordView, CoordDeleteView, ChallengeCreateView
) )
@ -13,7 +13,8 @@ urlpatterns = [
url(r'^round-recap/(?P<game_pk>\d+)/(?P<round_pk>\d+)/$', RoundRecapView.as_view(), name="round-recap-view"), url(r'^round-recap/(?P<game_pk>\d+)/(?P<round_pk>\d+)/$', RoundRecapView.as_view(), name="round-recap-view"),
url(r'^remove-coord/(?P<game_pk>\d+)/(?P<round_pk>\d+)/$', RemoveCoordView.as_view(), name="remove-coord"), url(r'^remove-coord/(?P<game_pk>\d+)/(?P<round_pk>\d+)/$', RemoveCoordView.as_view(), name="remove-coord"),
url(r'^end-recap/(?P<game_pk>\d+)/$', GameRecapView.as_view(), name="end-recap-view"), url(r'^end-recap/(?P<game_pk>\d+)/$', GameRecapView.as_view(), name="end-recap-view"),
url(r'^contribute/$', ContributeView.as_view(), name="contribute"), url(r'^create-challenge/$', ChallengeCreateView.as_view(), name="create-challenge"),
url(r'^country-autocomplete/$', CountryAutocomplete.as_view(), name='country-autocomplete', url(r'^list-challenge/$', ChallengeListView.as_view(), name="list-challenge"),
), url(r'^edit-challenge/(?P<pk>\d+)/$', EditChallengeView.as_view(), name="edit-challenge"),
url(r'^coord/(?P<pk>\d+)/delete/$', CoordDeleteView.as_view(), name="coord-delete"),
] ]

View File

@ -1,6 +1,7 @@
from django.conf import settings from django.conf import settings
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.views.generic import TemplateView, ListView from django.views.generic import TemplateView, ListView
from django.db.models import Avg
from django.views.generic.edit import CreateView, DeleteView, UpdateView from django.views.generic.edit import CreateView, DeleteView, UpdateView
from django.views.generic.detail import DetailView from django.views.generic.detail import DetailView
from django.views import View from django.views import View
@ -10,25 +11,12 @@ from django.urls import reverse_lazy
from braces import views from braces import views
from geogame.main.models import ( from geogame.main.models import (
Game, GameRound, Coord, User, Country Game, GameRound, Coord, User, Challenge
) )
from dal import autocomplete from dal import autocomplete
from geogame.main.forms import GuessForm, CoordForm, APIForm from geogame.main.forms import GuessForm, ChallengeCoordFormSet, APIForm, ChallengeForm
class CountryAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
# Don't forget to filter out results depending on the visitor !
if not self.request.user.is_authenticated:
return Country.objects.none()
qs = Country.objects.all().order_by('country')
if self.q:
qs = qs.filter(country__icontains=self.q)
return qs
class HomePageView(TemplateView): class HomePageView(TemplateView):
template_name = 'main/homepage.html' template_name = 'main/homepage.html'
@ -60,6 +48,12 @@ class HomePageView(TemplateView):
class ProfilePageView(views.LoginRequiredMixin, TemplateView): class ProfilePageView(views.LoginRequiredMixin, TemplateView):
template_name = 'main/profile.html' template_name = 'main/profile.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
user = self.request.user
context['user_challenges'] = Challenge.objects.filter(user=user)
return context
class UpdateAPIView(views.LoginRequiredMixin, UpdateView): class UpdateAPIView(views.LoginRequiredMixin, UpdateView):
model = User model = User
@ -70,20 +64,75 @@ class UpdateAPIView(views.LoginRequiredMixin, UpdateView):
return reverse_lazy('profile') return reverse_lazy('profile')
class ContributeView(views.LoginRequiredMixin, CreateView): class ChallengeCreateView(views.LoginRequiredMixin, CreateView):
model = Coord template_name = 'main/create_challenge_form.html'
form_class = CoordForm form_class = ChallengeForm
template_name = 'main/contribute_form.html'
def get_success_url(self):
return reverse_lazy('game:contribute')
def form_valid(self, form): def form_valid(self, form):
self.object = form.save(commit=False) self.object = form.save(commit=False)
self.object.user = self.request.user self.object.user = self.request.user
self.object.save() self.object.save()
messages.success(self.request, "Your coordinates have been added.") messages.success(self.request, 'Challenge Created Successfully, add some Co-ords')
return redirect(self.get_success_url()) return redirect(reverse_lazy('game:edit-challenge', args=(self.object.id,)))
class EditChallengeView(views.UserPassesTestMixin, TemplateView):
template_name = 'main/edit_challenge_form.html'
def test_func(self, user):
challenge = get_object_or_404(Challenge, pk=self.kwargs['pk'])
return challenge.user == user
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
challenge = get_object_or_404(Challenge, pk=self.kwargs['pk'])
context['challenge'] = challenge
context['formset'] = ChallengeCoordFormSet(queryset=Coord.objects.none())
context['coords'] = Coord.objects.filter(challenge=challenge)
return context
def post(self, *args, **kwargs):
challenge = get_object_or_404(Challenge, pk=self.kwargs['pk'])
formset = ChallengeCoordFormSet(self.request.POST)
if formset.is_valid():
for form in formset:
obj = form.save(commit=False)
obj.challenge = challenge
obj.user = self.request.user
obj.save()
#XX prevent duplicates
messages.success(self.request, 'Co-ords Updated Successfully')
return redirect(reverse_lazy('game:edit-challenge', args=(challenge.id,)))
messages.error(self.request, 'The form was invalid - something has gone wrong')
return redirect(reverse_lazy('game:edit-challenge', args=(challenge.id,)))
class CoordDeleteView(views.UserPassesTestMixin, DeleteView):
model = Coord
def test_func(self, user):
coord = self.get_object()
return coord.user == user
def get_success_url(self):
challenge = self.object.challenge
messages.success(self.request, 'Coord Removed Successfully')
return reverse_lazy('game:edit-challenge',args=(challenge.id,))
class ChallengeListView(views.LoginRequiredMixin, ListView):
template_name = 'main/challenge_list.html'
context_object_name = 'challenges'
paginate_by = 50
def get_queryset(self):
ids = Coord.objects.filter(challenge__isnull=False).\
order_by().\
values('challenge').\
distinct()
return Challenge.objects.filter(id__in=ids).order_by('average')
class NewGameView(views.LoginRequiredMixin, View): class NewGameView(views.LoginRequiredMixin, View):
@ -91,7 +140,11 @@ class NewGameView(views.LoginRequiredMixin, View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
user = self.request.user user = self.request.user
user.deactive_games() user.deactive_games()
game_pk, round_pk = user.generate_new_game() if request.GET.get('challenge'):
challenge = get_object_or_404(Challenge, pk=request.GET.get('challenge'))
game_pk, round_pk = challenge.setup_challenge(user)
else:
game_pk, round_pk = user.generate_new_game()
return redirect( return redirect(
reverse_lazy( reverse_lazy(
@ -151,30 +204,60 @@ class RemoveCoordView(View):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
round = get_object_or_404(GameRound, pk=self.kwargs.get('round_pk', 0)) round = get_object_or_404(GameRound, pk=self.kwargs.get('round_pk', 0))
game = get_object_or_404(Game, pk=self.kwargs.get('game_pk', 0)) game = get_object_or_404(Game, pk=self.kwargs.get('game_pk', 0))
qs = Coord.objects.all() dodgy_coord = round.coord
if round.game.country: dodgy_coord.report()
qs = qs.filter(country=round.game.country)
coords = qs.order_by('?')[:5]
current_coords = GameRound.objects.filter(game=game).exclude(id=round.id).values_list('coord__id', flat=True) if game.challenge:
for coord in coords: round.score = 30000
if coord.id not in current_coords: round.save()
round.coord = coord next = round.order + 1
round.save() next_round = GameRound.objects.filter(
break game=game,
order=next,
).first()
if next_round:
return redirect(
reverse_lazy(
'game:round-view',
kwargs={
'game_pk': game.pk,
'round_pk': next_round.pk,
}
)
)
else:
return redirect(
reverse_lazy(
'game:end-recap-view',
kwargs={
'game_pk': game.pk,
}
)
)
else:
qs = Coord.objects.filter(reports=0)
coords = qs.order_by('?')[:5]
current_coords = GameRound.objects.filter(game=game).exclude(id=round.id).values_list('coord__id', flat=True)
for coord in coords:
if coord.id not in current_coords:
round.coord = coord
round.save()
break
return redirect( return redirect(
reverse_lazy( reverse_lazy(
'game:round-view', 'game:round-view',
kwargs={ kwargs={
'game_pk': round.game.pk, 'game_pk': game.pk,
'round_pk': round.pk, 'round_pk': round.pk,
} }
) )
) )
class RoundRecapView(views.UserPassesTestMixin, TemplateView): class RoundRecapView(views.UserPassesTestMixin, TemplateView):
template_name = 'main/round_recap.html' template_name = 'main/round_recap.html'
@ -195,13 +278,19 @@ class RoundRecapView(views.UserPassesTestMixin, TemplateView):
context['game_id'] = round.game.id context['game_id'] = round.game.id
context['distance'] = "{0:.3f}".format(round.get_distance()) context['distance'] = "{0:.3f}".format(round.get_distance())
if round.order == 4: next_round = GameRound.objects.filter(
game=round.game,
order=round.order + 1
).first()
if not next_round:
#not every game is part of a challenge, so try updated the challenge average
try:
round.game.challenge.update_average_score()
except:
pass
context['last_round'] = True context['last_round'] = True
else: else:
next_round = GameRound.objects.get(
game=round.game,
order=round.order+1
)
context['next_round_id'] = next_round.id context['next_round_id'] = next_round.id
return context return context
@ -214,7 +303,6 @@ class GameRecapView(views.UserPassesTestMixin, TemplateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(GameRecapView, self).get_context_data(**kwargs) context = super(GameRecapView, self).get_context_data(**kwargs)
user = self.request.user
game_id = self.kwargs.get('game_pk', 0) game_id = self.kwargs.get('game_pk', 0)
game = get_object_or_404(Game, pk=game_id) game = get_object_or_404(Game, pk=game_id)
@ -229,6 +317,17 @@ class GameRecapView(views.UserPassesTestMixin, TemplateView):
) )
distance_total += round.get_distance() distance_total += round.get_distance()
context['average_distance'] = "{0:.3f}".format(distance_total / 5)
context['results'] = coord_results context['results'] = coord_results
context['average_distance'] = GameRound.objects.filter(game=game)\
.aggregate(Avg('result'))\
.get('result__avg', 0)
# not every game is part of a challenge, so keep this in a try
try:
context['all_average'] = game.challenge.average
except:
pass
return context return context
# handle reports differently if in a challenge (skip with score of 30000 instead of finding a random one)

View File

@ -47,6 +47,7 @@ INSTALLED_APPS = [
'django_extensions', 'django_extensions',
'dal', 'dal',
'dal_select2', 'dal_select2',
'dynamic_formsets',
'geogame.main', 'geogame.main',
'django_countries', 'django_countries',

View File

@ -11,7 +11,9 @@
<link rel="shortcut icon" type="image/png" href="{% static 'favicon.ico' %}"/> <link rel="shortcut icon" type="image/png" href="{% static 'favicon.ico' %}"/>
<title>Geogame</title> <title>Geogame</title>
</head> </head>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
<body> <body>
<nav class="navbar navbar-expand-lg navbar-light bg-light"> <nav class="navbar navbar-expand-lg navbar-light bg-light">
<img id="logo" src="{% static 'brand.png' %}"></img> <img id="logo" src="{% static 'brand.png' %}"></img>
@ -29,7 +31,7 @@
<a href="{% url 'profile' %}" class="nav-link">Profile</a> <a href="{% url 'profile' %}" class="nav-link">Profile</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="{% url 'game:contribute' %}" class="nav-link">Contribute</a> <a href="{% url 'game:create-challenge' %}" class="nav-link">Contribute</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="{% url 'account_logout' %}" class="nav-link">Logout</a> <a href="{% url 'account_logout' %}" class="nav-link">Logout</a>
@ -58,7 +60,4 @@
</div> </div>
</div> </div>
</div> </div>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
</body> </body>

View File

@ -28,9 +28,6 @@
<li class="nav-item"> <li class="nav-item">
<a href="{% url 'profile' %}" class="nav-link">Profile</a> <a href="{% url 'profile' %}" class="nav-link">Profile</a>
</li> </li>
<li class="nav-item">
<a href="{% url 'game:contribute' %}" class="nav-link">Contribute</a>
</li>
<li class="nav-item"> <li class="nav-item">
<a href="{% url 'account_logout' %}" class="nav-link">Logout</a> <a href="{% url 'account_logout' %}" class="nav-link">Logout</a>
</li> </li>

View File

@ -4,7 +4,7 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-lg-10 offset-lg-1 content"> <div class="col-lg-10 offset-lg-1 content">
<h1 class="section-header">API key for for {{user.email}}</h1> <h1 class="section-header">Details for {{user.email}}</h1>
</div> </div>
</div> </div>
</div> </div>
@ -15,7 +15,7 @@
{{ form.non_field_errors }} {{ form.non_field_errors }}
{% csrf_token %} {% csrf_token %}
{{form.as_p}} {{form.as_p}}
<button type="submit" class="btn btn-primary">Update API Key</button> <button type="submit" class="btn btn-primary">Update User Info</button>
</form> </form>
</div> </div>
</div> </div>

View File

@ -0,0 +1,49 @@
{% extends 'game_base.html' %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-lg-10 offset-lg-1 content">
<h1 class="section-header">All Challenges</h1>
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-lg-10 offset-lg-1 content">
<table>
<tr>
<th>Name</th>
<th>Average</th>
<th></th>
</tr>
{% for challenge in challenges %}
<tr>
<td>{{challenge.name}}</td>
<td>{{challenge.average}}</td>
<td><a class="btn btn-success btn-block" href="{% url 'game:new-game' %}?challenge={{challenge.id}}" role="button">Play</a></td>
</tr>
{% endfor %}
</table>
</div>
</div>
{% if is_paginated %}
<div class="pagination">
<span class="page-links">
{% if page_obj.has_previous %}
<a href="{% url 'supplier:my-suppliers-list' %}?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="page-current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="{% url 'supplier:my-suppliers-list' %}?page={{ page_obj.next_page_number }}">next</a>
{% endif %}
</span>
</div>
{% endif %}
</div>
{% endblock %}

View File

@ -0,0 +1,35 @@
{% extends 'game_base.html' %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-lg-10 offset-lg-1 content">
<h1 class="section-header">Create Challenge</h1>
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-lg-10 offset-lg-1 content">
<form action="" method="POST">
{{ form.non_field_errors }}
{% csrf_token %}
<div class="form-group form-row">
<label for="staticName" class="col-sm-4 col-form-label">Name</label>
<div class="col-sm-8">
{{form.name}}
</div>
</div>
<div class="form-group form-row">
<div class="col-sm-12">
<a class="btn btn-danger pull-left" href="{% url 'profile' %}">Cancel</a>
<button type="submit" class="btn btn-primary pull-right">Save</button>
</div>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,92 @@
{% extends 'base.html' %}
{% load staticfiles%}
{% block content %}
<div class="container">
<div class="row">
<div class="col-lg-10 offset-lg-1 content">
<h1 class="section-header">Edit Challenge {{challenge.name}}</h1>
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-lg-10 offset-lg-1 content">
{% if form.errors %}
{% for field in form %}
{% for error in field.errors %}
<div class="alert alert-danger">
<strong>{{ error|escape }}</strong>
</div>
{% endfor %}
{% endfor %}
{% for error in form.non_field_errors %}
<div class="alert alert-danger">
<strong>{{ error|escape }}</strong>
</div>
{% endfor %}
{% endif %}
<table>
<tr>
<th>Latitude</th>
<th>Longitude</th>
<th></th>
</tr>
{% for coord in coords %}
<tr>
<td>
{{ coord.lat }}
</td>
<td>
{{ coord.lng }}
</td>
<td style="width:105px">
<form action="{% url 'game:coord-delete' pk=coord.pk %}" method="POST">
{% csrf_token %}
<input type="submit" class="btn btn-danger btn-block" value="Remove" name="delete" style="margin-right:0">
</form>
</td>
</tr>
{% endfor %}
</table>
<hr>
<form id="coordForm" method="post" action="">
{% csrf_token %}
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
{% for form in formset.forms %}
<tr>
<td>{{ form.lat }}</td>
<td>{{ form.lng }}</td>
<td style="width:105px"></td>
</tr>
{% endfor %}
</tbody>
</table>
{{ formset.management_form }}
</form>
<input class="btn btn-primary pull-right" type="submit" value="Save" form="coordForm">
</div>
</div>
</div>
<script src="{% static 'dynamic_formsets/jquery.formset.js' %}" type="text/javascript"> </script>
<script type="text/javascript">
$('#coordForm').formset();
// function defer(method) {
// if (window.jQuery) {
// $('.individual-form').formset();
// } else {
// setTimeout(function() { defer(method) }, 50);
// }
// }
</script>
{% endblock %}

View File

@ -14,7 +14,7 @@
{% endif %} {% endif %}
<div class="alert alert-info"> <div class="alert alert-info">
Your average guess was {{average_distance}}km away. Your average guess was {{average_distance}}km away.{% if all_average %} The average for this challenge is {{all_average}}km{%endif%}
</div> </div>
<div id="map" class="map"></div> <div id="map" class="map"></div>

View File

@ -15,17 +15,20 @@
{% if has_api_key %} {% if has_api_key %}
{% if existing_game %} {% if existing_game %}
<a class="btn btn-info btn-lg btn-block" href="{% url 'game:round-view' game_pk=existing_game.id round_pk=existing_round.id %}" role="button">Continue Last Game</a> <a class="btn btn-info btn-lg btn-block" href="{% url 'game:round-view' game_pk=existing_game.id round_pk=existing_round.id %}" role="button">Continue Last Game</a>
<a class="btn btn-success btn-lg btn-block" href="{% url 'game:new-game' %}" role="button">New Game</a> <a class="btn btn-success btn-lg btn-block" href="{% url 'game:new-game' %}" role="button">New Random Game</a>
<a class="btn btn-warning btn-lg btn-block" href="{% url 'game:list-challenge' %}" role="button">View Challenges</a>
{% else %} {% else %}
<button type="button" class="btn btn-info btn-lg btn-block" disabled>Continue Last Game</button> <button type="button" class="btn btn-info btn-lg btn-block" disabled>Continue Last Game</button>
<a class="btn btn-success btn-lg btn-block" href="{% url 'game:new-game' %}" role="button">New Game</a> <a class="btn btn-success btn-lg btn-block" href="{% url 'game:new-game' %}" role="button">New Random Game</a>
<a class="btn btn-warning btn-lg btn-block" href="{% url 'game:list-challenge' %}" role="button">View Challenges</a>
{% endif %} {% endif %}
{% else %} {% else %}
<div class="alert alert-warning"> <div class="alert alert-warning">
You must set an api key on your profile page to play the game. Please read the FAQ for more info. You must set an api key on your profile page to play the game. Please read the FAQ for more info.
</div> </div>
<button type="button" class="btn btn-info btn-lg btn-block" disabled>Continue Last Game</button> <button type="button" class="btn btn-info btn-lg btn-block" disabled>Continue Last Game</button>
<button type="button" class="btn btn-success btn-lg btn-block" disabled>New Game</button> <button type="button" class="btn btn-success btn-lg btn-block" disabled>New Random Game</button>
<button type="button" class="btn btn-warning btn-lg btn-block" disabled>View Challenges</button>
{% endif %} {% endif %}
{% else %} {% else %}
@ -60,7 +63,7 @@
<p>Sometimes a coordinate with no valid streetview is selected for you, you will just get a black screen.</p> <p>Sometimes a coordinate with no valid streetview is selected for you, you will just get a black screen.</p>
<p>If this occurs please click the "Broken Streetview" button and you will be provided with a new scene and the faulty coordinate will be removed from the database.</p> <p>If this occurs please click the "Broken Streetview" button and you will be provided with a new scene and the faulty coordinate will be removed from the database.</p>
<p><strong>There isn't a great variety of countries, why?</strong></p> <p><strong>There isn't a great variety of countries, why?</strong></p>
<p>I am relying on crowd sourcing of playable scenes, please consider helping out by <a href="{% url 'game:contribute' %}">adding a few coordinates of your own</a>.</p> <p>I am relying on crowd sourcing of playable scenes, please consider helping out by <a href="{% url 'game:create-challenge' %}">adding a few coordinates of your own</a>.</p>
<p>You get to keep track of the coordinates you add, and see how well (or badly) other people do on them!</p> <p>You get to keep track of the coordinates you add, and see how well (or badly) other people do on them!</p>
<p>For now I have managed to find some data on uk coordinates that I can use, but its not so easy getting data for other countries.</p> <p>For now I have managed to find some data on uk coordinates that I can use, but its not so easy getting data for other countries.</p>
<p><strong>I didn't receive my email reset password, what gives?</strong></p> <p><strong>I didn't receive my email reset password, what gives?</strong></p>

View File

@ -16,7 +16,7 @@
<a class="btn btn-primary btn-lg" style="margin-top:16px" href="{% url 'account_change_password' %}">Change Password</a> <a class="btn btn-primary btn-lg" style="margin-top:16px" href="{% url 'account_change_password' %}">Change Password</a>
</p> </p>
<p> <p>
<a class="btn btn-primary btn-lg" href="{% url 'api-key-view' pk=user.pk %}">API key</a> <a class="btn btn-primary btn-lg" href="{% url 'api-key-view' pk=user.pk %}">Edit Details</a>
</p> </p>
</div> </div>
</div> </div>
@ -25,6 +25,7 @@
<div class="row"> <div class="row">
<div class="col-lg-10 offset-lg-1 content"> <div class="col-lg-10 offset-lg-1 content">
<h3 class="section-header">Played Games</h3> <h3 class="section-header">Played Games</h3>
<hr>
<p>Coming soon</p> <p>Coming soon</p>
</div> </div>
</div> </div>
@ -33,7 +34,23 @@
<div class="row"> <div class="row">
<div class="col-lg-10 offset-lg-1 content"> <div class="col-lg-10 offset-lg-1 content">
<h3 class="section-header">Your Contributions</h3> <h3 class="section-header">Your Contributions</h3>
<p>Coming soon</p> <hr>
<table>
<tr>
<th>Challenge Name</th>
<th>Average Score</th>
<th></th>
</tr>
{% for challenge in user_challenges %}
<tr>
<td>{{challenge.name}}</td>
<td>{{challenge.average}}</td>
<td><a class="btn btn-warning btn-block" href="{% url 'game:edit-challenge' pk=challenge.pk %}" role="button">Edit</a></td>
</tr>
{% endfor %}
</table>
<hr>
<a class="btn btn-primary btn-lg" href="{% url 'game:create-challenge' %}">Create a New Challenge</a>
</div> </div>
</div> </div>
</div> </div>