Added tonnes of features, lets see what breaks.

This commit is contained in:
root
2019-11-26 02:13:47 +01:00
parent ddbbfb5ee4
commit e2d75c3ea0
17 changed files with 500 additions and 102 deletions

View File

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

View File

@@ -5,26 +5,17 @@ from django.conf import settings
from django.forms import widgets
from django.forms.utils import to_current_timezone
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from django.forms import modelformset_factory
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:
model = Coord
fields = ('lat', 'lng', 'country')
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')
model = Challenge
fields = ('name',)
class GuessForm(forms.ModelForm):
@@ -47,7 +38,7 @@ class APIForm(forms.ModelForm):
class Meta:
model = User
fields = ('api_key',)
fields = ('api_key', 'display_name',)
class CustomUserCreationForm(UserCreationForm):
@@ -62,3 +53,12 @@ class CustomUserChangeForm(UserChangeForm):
class Meta:
model = User
fields = ('username', 'email')
ChallengeCoordFormSet = modelformset_factory(
Coord,
fields=('lat', 'lng',),
extra=1,
)

View File

@@ -20,13 +20,18 @@ class Command(BaseCommand):
with transaction.atomic():
country = Country.objects.get(country='United Kingdom')
country = Country.objects.get(country="United States of America")
user = User.objects.first()
with open('/home/ubuntu/lat_lng.csv', 'r') as csvfile:
datareader = csv.reader(csvfile)
for row in datareader:
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']:
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.conf import settings
from django.db import models, transaction, IntegrityError
from django.db.models import Avg
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
@@ -13,8 +14,6 @@ from django.shortcuts import reverse
from django.contrib.auth.models import AbstractUser
from django.utils import timezone
from django_countries.fields import CountryField
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)
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)
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(
start=timezone.now(),
user=self,
score=0,
active=True,
)
qs = Coord.objects.all()
if country:
qs = qs.filter(country=country)
qs = Coord.objects.filter(reports=0)
coords = qs.order_by('?')[:5]
for i, coord in enumerate(coords):
round = GameRound.objects.create(
@@ -52,34 +50,65 @@ class User(AbstractUser):
return Game.objects.filter(user=self).update(active=False)
class Country(models.Model):
country = models.CharField(_('Country'), max_length=255, null=False, blank=False)
class Challenge(models.Model):
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):
return self.country
def update_average_score(self):
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):
lng = models.CharField(_('longitude'), 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,
related_name='coord_user',
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):
challenge = models.ForeignKey(Challenge, models.PROTECT,
related_name='game_challenge',
null=True, blank=True)
start = models.DateTimeField()
user = models.ForeignKey(User, models.PROTECT,
related_name='game_user',
null=False, blank=False)
score = models.PositiveIntegerField()
active = models.BooleanField()
country = models.ForeignKey(Country, models.PROTECT,
related_name='game_country',
null=True, blank=True)
def get_rounds(self):
return GameRound.objects.filter(game=self)
@@ -95,10 +124,18 @@ class GameRound(models.Model):
order = models.IntegerField(_('round order'))
guess_lat = models.CharField(_('latitude'), 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):
if not self.guess_lat or not self.guess_lng:
self.distance = 0
self.save()
return 0
actual_coord = (self.coord.lat, self.coord.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 geogame.main.views import (
RoundView, RoundRecapView, GameRecapView, NewGameView,
ContributeView, CountryAutocomplete, RemoveCoordView
RoundView, RoundRecapView, GameRecapView, NewGameView, ChallengeListView,
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'^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'^contribute/$', ContributeView.as_view(), name="contribute"),
url(r'^country-autocomplete/$', CountryAutocomplete.as_view(), name='country-autocomplete',
),
url(r'^create-challenge/$', ChallengeCreateView.as_view(), name="create-challenge"),
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.shortcuts import get_object_or_404, redirect
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.detail import DetailView
from django.views import View
@@ -10,25 +11,12 @@ from django.urls import reverse_lazy
from braces import views
from geogame.main.models import (
Game, GameRound, Coord, User, Country
Game, GameRound, Coord, User, Challenge
)
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):
template_name = 'main/homepage.html'
@@ -60,6 +48,12 @@ class HomePageView(TemplateView):
class ProfilePageView(views.LoginRequiredMixin, TemplateView):
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):
model = User
@@ -70,20 +64,75 @@ class UpdateAPIView(views.LoginRequiredMixin, UpdateView):
return reverse_lazy('profile')
class ContributeView(views.LoginRequiredMixin, CreateView):
model = Coord
form_class = CoordForm
template_name = 'main/contribute_form.html'
def get_success_url(self):
return reverse_lazy('game:contribute')
class ChallengeCreateView(views.LoginRequiredMixin, CreateView):
template_name = 'main/create_challenge_form.html'
form_class = ChallengeForm
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
self.object.save()
messages.success(self.request, "Your coordinates have been added.")
return redirect(self.get_success_url())
messages.success(self.request, 'Challenge Created Successfully, add some Co-ords')
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):
@@ -91,7 +140,11 @@ class NewGameView(views.LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
user = self.request.user
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(
reverse_lazy(
@@ -151,30 +204,60 @@ class RemoveCoordView(View):
def post(self, request, *args, **kwargs):
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))
qs = Coord.objects.all()
if round.game.country:
qs = qs.filter(country=round.game.country)
coords = qs.order_by('?')[:5]
dodgy_coord = round.coord
dodgy_coord.report()
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
if game.challenge:
round.score = 30000
round.save()
next = round.order + 1
next_round = GameRound.objects.filter(
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(
reverse_lazy(
'game:round-view',
kwargs={
'game_pk': round.game.pk,
'game_pk': game.pk,
'round_pk': round.pk,
}
)
)
class RoundRecapView(views.UserPassesTestMixin, TemplateView):
template_name = 'main/round_recap.html'
@@ -195,13 +278,19 @@ class RoundRecapView(views.UserPassesTestMixin, TemplateView):
context['game_id'] = round.game.id
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
else:
next_round = GameRound.objects.get(
game=round.game,
order=round.order+1
)
context['next_round_id'] = next_round.id
return context
@@ -214,7 +303,6 @@ class GameRecapView(views.UserPassesTestMixin, TemplateView):
def get_context_data(self, **kwargs):
context = super(GameRecapView, self).get_context_data(**kwargs)
user = self.request.user
game_id = self.kwargs.get('game_pk', 0)
game = get_object_or_404(Game, pk=game_id)
@@ -229,6 +317,17 @@ class GameRecapView(views.UserPassesTestMixin, TemplateView):
)
distance_total += round.get_distance()
context['average_distance'] = "{0:.3f}".format(distance_total / 5)
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
# handle reports differently if in a challenge (skip with score of 30000 instead of finding a random one)